iterating over and removing from a map
我在做:
1 2 3
| for (Object key : map. keySet())
if (something )
map. remove(key ); |
它引发了一个ConcurrentModificationException,所以我把它改为:
1 2 3
| for (Object key : new ArrayList <Object >(map. keySet()))
if (something )
map. remove(key ); |
这以及修改映射的任何其他过程都位于同步块中。
有更好的解决方案吗?
如果没有人提出更好的解决方案,首先要说"不",然后打勾;)
- 如果这个方法和修改映射的另一个方法在同步块中,我不明白为什么你要做什么?也许我不完全理解你的问题?你能把剩下的代码贴出来吗?
- 谢谢你的回答-我学到了一些东西,我希望一半的回答者也学会了!(乔希、文森特、维克)
- @拉爱德华德,这个问题和它被接受的答案比其他国际海事组织更简洁。
下面是一个代码示例,用于在for循环中使用迭代器删除条目。
1 2 3 4 5 6 7 8 9 10 11 12 13
| Map <String, String > map = new HashMap <String, String >() {
{
put ("test", "test123");
put ("test2", "test456");
}
};
for(Iterator <Map. Entry<String, String >> it = map. entrySet(). iterator(); it. hasNext(); ) {
Map. Entry<String, String > entry = it. next();
if(entry. getKey(). equals("test")) {
it. remove();
}
} |
- 所以,你应该这样做:it.remove(),而不是collection.remove(key)在循环中。太棒了!
- 这个答案对于Java 8之前的代码是很好的,但是如果使用Java 8,ELRON的答案是更好的。stackoverflow.com/a/29187813/2077574
- 这在Android中也很有效。
- 像魅力一样工作谢谢
- 如果在此映射中添加了一个项,而该项被循环以删除项,那么它是否仍会引发一个ConcurrentModificationException?
- hashmap有一个fail-fast迭代器。那么为什么.remove()不抛出ConcurrentModificationException呢?
- 自Java 8以来,您也可以使用它:EDCOX1 OR 9
至于Java 8,你可以这样做:
1
| map.entrySet().removeIf(e -> <boolean expression>); |
使用真正的迭代器。
1 2 3 4 5 6 7 8
| Iterator<Object> it = map.keySet().iterator();
while (it.hasNext())
{
it.next();
if (something)
it.remove();
} |
实际上,您可能需要迭代entrySet(),而不是keySet(),以使其工作。
- 这看起来确实比我迭代入口集的解决方案要优雅一些。尽管从键集中删除会从映射中删除一些内容(即键集可以是副本),但这并不十分明显。
- 不好意思重复评论。我已经确认,从键集中移除确实会从映射中移除,尽管不如从条目集中移除那么明显。
- 迭代密钥集是实现这一点的方法。
- (尽管为了匹配问题,应该是Iterator。)
- 汤姆,在第一个循环中,他使用的是"for(object key):",所以我使用了一个对象迭代器。
- 在调用remove之前,必须调用Iterator.next()或本例中的it.next(),否则将出现IllegalStateException。
- @布赖安,你说得对,我忘了。
- 对于记录,这也适用于映射值:Iterator it = map.values().iterator(),然后it.remove()将删除它的条目。
- hashmap有一个fail-fast迭代器。那么为什么.remove()不抛出ConcurrentModificationException呢?
- @当其他东西同时修改容器时,sumanth232失败快速迭代器失败。您可以按预期使用迭代器。在这个答案中,除了迭代器之外,没有任何东西在修改键集,并且它也被转换为拥有该集的映射。一次只对每个容器执行一个操作;没有并发性。
is there a better solution?
当然,在一个语句中有一种更好的方法,但是这取决于基于哪些元素被删除的条件。
例如:删除所有测试value的元素,然后使用以下内容:
更新它可以使用Java 8中的lambda表达式在一行中完成。
1
| map.entrySet().removeIf(e-> <boolean expression> ); |
我知道这个问题太老了,但是更新更好的方法并没有什么害处。
- 这可能是一个古老的问题,但这确实帮助了我。我从来没有听说过collections.singleton(),我不知道可以通过对values()调用removeall()从映射中删除元素!谢谢您。
- 删除键为test的元素:map.keyset().removeall(collections.singleton("test"));
- @marouanelakhal删除元素,哪个键是test,你不就做map.remove("test");吗?
- @正如问题中所述,map.remove(key)在map.keyset()上循环时抛出了一个ConcurrentModificationException。
- @Marouanelakhal如果你已经知道了你想要移除的地图元素的关键,为什么你首先要循环?地图中只能有一个具有相同键的条目。
当前地图
您可以使用java.util.concurrent.ConcurrentHashMap。
它实现了ConcurrentMap(扩展了Map接口)。
例如。:
1 2 3 4 5 6 7
| Map <Object, Content > map = new ConcurrentHashMap <Object, Content >();
for (Object key : map. keySet()) {
if (something ) {
map. remove(key );
}
} |
这种方法不会影响您的代码。只有Map型不同。
- 此方法的问题在于ConcurrentHashMap不允许将"null"作为值(或键),因此,如果您处理的是包含null的映射,则不能使用此方法。
Java 8支持一种更加声明性的迭代方法,因为我们指定了我们想要的结果,而不是如何计算它。新方法的好处在于它更可读,不易出错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void mapRemove () {
Map <Integer, String > map = new HashMap <Integer, String >() {
{
put (1, "one");
put (2, "two");
put (3, "three");
}
};
map. forEach( (key, value ) -> {
System. out. println("Key:" + key +"\t" +" Value:" + value );
});
map. keySet(). removeIf(e ->(e >2)); // <-- remove here
System. out. println("After removing element");
map. forEach( (key, value ) -> {
System. out. println("Key:" + key +"\t" +" Value:" + value );
});
} |
结果如下:
1 2 3 4 5 6
| Key: 1 Value : one
Key: 2 Value : two
Key: 3 Value : three
After removing element
Key: 1 Value : one
Key: 2 Value : two |
在遍历映射时,必须使用Iterator安全地删除元素。
我同意保罗·汤布林的观点。我通常使用keyset的迭代器,然后根据该键的值确定条件:
1 2 3 4 5 6 7 8
| Iterator <Integer > it = map. keySet(). iterator();
while(it. hasNext()) {
Integer key = it. next();
Object val = map. get(key );
if (val. shouldBeRemoved()) {
it. remove();
}
} |
- 您应该使用entryset,而不是使用keyset,并且每次都执行get。findbugs甚至有一个检测器:findbugs.sourceforge.net/&hellip;
另一种更详细的方法
1 2 3 4 5 6 7 8 9 10
| List<SomeObject> toRemove = new ArrayList<SomeObject>();
for (SomeObject key: map.keySet()) {
if (something) {
toRemove.add(key);
}
}
for (SomeObject key: toRemove) {
map.remove(key);
} |
这也应该有效。
1 2 3 4 5 6 7 8 9 10 11
| ConcurrentMap <Integer, String > running = ... create and populate map
Set <Entry <Integer, String >> set = running. entrySet();
for (Entry <Integer, String > entry : set )
{
if (entry. getKey()>600000)
{
set. remove(entry );
}
} |
也许您可以在地图上迭代,寻找要删除的键,并将它们存储在单独的集合中。然后从地图中删除键集合。在迭代时修改映射通常是不受欢迎的。如果地图很大,这个想法可能会受到怀疑。
- 如果你每个人都能用一个,那肯定是更优雅的解决方案。
1 2 3 4 5 6 7 8 9 10
| Set s =map. entrySet();
Iterator iter = s. iterator();
while (iter. hasNext()) {
Map.Entry entry =(Map.Entry)iter. next();
if("value you need to remove". equals(entry. getKey())) {
map. remove();
}
} |
- 还有一些解释。
- 我认为map.remove会不会删除当前的修改,除非