以块的形式读取ConcurrentHashMap(或类似的)

时间:2016-01-09 00:47:30

标签: java concurrency hashmap java.util.concurrent

我有一个程序,它有ConcurrentHashMap,其中不同的线程可以在地图中添加/删除项目。

我有兴趣了解以25件物品的形式阅读地图的最佳方法。 我想要做的是这样的:用户点击按钮并从地图中读取25个项目(与订单无关)。之后他可以点击"下一步"按钮并读取另外25个项目(与前25个项目不同),依此类推。

我不确定我是否可以使用ConcurrentHashMap执行此操作。我不想使用数据库,我想把它留在内存中。 我不认为将Map转换为ArrayList会有所帮助,因为大部分时间都会在地图中添加/删除项目。

我愿意接受任何解决方案,甚至是第三方图书馆。

更新:我也没有与ConcurrentHashMap绑定。我只是在寻找最佳解决方案

更新2 :他们的密钥为String

由于

2 个答案:

答案 0 :(得分:6)

在您的情况下,由于严格按照字符串键排序,ConcurrentSkipListMap是一种方法。

  • 它既是concurrent又是navigable,可以经常用来代替ConcurrentHashMap。
  • get / put / removeO(log N)一样快。
  • 作为奖励,您将获得免费的自然遍历订单。

要从ConcurrentSkipListMap获取下一页,请使用上一页的最后一个键作为锚点调用tailMap,然后从结果子图中构造迭代器或流:

    return map.tailMap(lastKey, false).entrySet()
            .stream()
            .limit(pageSize)
            .collect(Collectors.toList());

请注意,即使从地图中删除了锚键,tailMap也会成功。迭代将从大于锚的下一个键开始。

如果密钥没有严格排序,或者需要O(1)复杂度,那么您可能更喜欢另一种建议 - 以单元索引的顺序遍历的开放式散列表。但是,标准Java类中没有这样的实现。这种映射的线程安全性通常通过锁定来实现。

答案 1 :(得分:0)

如果您不想使用concurrenthashmap,可以考虑使用linkedhashmap,并进行同步。
Linkedhashmap按插入顺序存储,因此您可以迭代它。 但是,您需要保留lastViewedElement,并在删除它时,使用新的有效值更新它,以便您可以从那里进行迭代。

它应该正确同步(可能是手动),因为它不是线程安全的。粗略的实施将是:
(我是通过头部而不是IDE来做这件事,所以它可能会错过任何东西。而且我的仿制品不适合鼻烟,你可能也不需要仿制药......)

public class MyMagicMap<X,Y> implements Map
    private LinkedHashMap<X,Y> innerMap = new LinkedHashMap<>();
    X lastKey = null;

    @Override
    public void put(X key,Y value) {
        synchronized(MyMagicMap.class) {
            if(innerMap.contains(key)) {
                innerMap.remove(key);
            }
            innerMap.put(key, value);
        }
    }

    @Override
    public Y remove(X key) {
        synchronized(MyMagicMap.class) {
            if(key.equals(lastKey) {
                lastKey = getKeyBefore(lastKey);
            }
            return innerMap.remove(key);
        }
    }

    private X getKeyBefore(X oldLastKey) {
        Iterator<X> it = innerMap.KeySet.iterator();
        for(X key : it  ) {
            if(key.equals(oldLastKey)) {
                return it.next();
            }
        }
    }

    public Map<X,Y> getNextBatch(int count) {
         synchronized(MyMagicMap.class) {

             Map<X,Y> resultMap = new HashMap();
             Iterator<X> it = innerMap.keySet().iterator();
             X lastKey = scanForLastKey(it);
             if(lastKey == null) {
                 return resultMap;
             }
             resultMap.put(lastKey, innerMap.get(key);
             for(int i; i < count-1; i++) {
                 if(it.hasNext()) {
                     X key = it.next();
                     resultMap.put(X, innerMap.get(key));
                 }
             }
        }
    }

    private X scanForLastKey(Iterator<X> iterator) {
        if(lastKey == null) {
            iterator.next();
        }
        while(iterator.hasNext()) {
            X key = iterator.next();
            if(key.equals(lastKey)) {
                return key;
            }
        }
        return null;
    }




    // other map methods can probably go without synchronization
}

要注意的是相关块上有线程同步,只有一个线程应该这样做 另请注意,删除方法表现不佳(O(N))
如果你想更快地完成它,你将不得不实现自己的LinkedHashMap,它允许你有效地确定下一行。听起来很难,直到你意识到你只需要一个HashMap&gt;三元组具有前一个和下一个的键值。再次同步put和remove操作,这是为了保持列表的完整性,即

void put(X key, Y value) {
    // synchronize this
    Triplet<X,Y,X> lastValueInMap = innerMap.get(endOfMapKey);
    lastValueInMap.rightElement = key;
    innerMap.put(X, new Triplet(endOfMapKey, value, null);
    endOfMapKey = key;
}
Y remove(X key) {
    // synchronize this.
    Triplet<X,Y,X> valueTriplet = innerMap.remove(key);
    if(valueTriplet.left != null)
        if(valueTriplet.right != null) {
            innerMap.get(valueTriplet.left).right = valueTriplet.right;
            innerMap.get(valueTriplet.right).left=valueTriplet.left;
        } else
        // etc.
     }
}

最后要注意的是put方法,如果它们已经存在而不是更新值,则显式删除键。由于LinkedHashMap的更新不会修改插入顺序,因此如果未完成删除,则表示已批量读取并稍后更新的任何密钥K将永远不会显示在较新的批处理中。