我有一个程序,它有ConcurrentHashMap
,其中不同的线程可以在地图中添加/删除项目。
我有兴趣了解以25件物品的形式阅读地图的最佳方法。 我想要做的是这样的:用户点击按钮并从地图中读取25个项目(与订单无关)。之后他可以点击"下一步"按钮并读取另外25个项目(与前25个项目不同),依此类推。
我不确定我是否可以使用ConcurrentHashMap
执行此操作。我不想使用数据库,我想把它留在内存中。
我不认为将Map
转换为ArrayList
会有所帮助,因为大部分时间都会在地图中添加/删除项目。
我愿意接受任何解决方案,甚至是第三方图书馆。
更新:我也没有与ConcurrentHashMap
绑定。我只是在寻找最佳解决方案
更新2 :他们的密钥为String
由于
答案 0 :(得分:6)
在您的情况下,由于严格按照字符串键排序,ConcurrentSkipListMap
是一种方法。
get
/ put
/ remove
与O(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将永远不会显示在较新的批处理中。