【问题标题】:iterating over and removing from a map [duplicate]迭代并从地图中删除[重复]
【发布时间】:2010-12-25 11:42:22
【问题描述】:

我在做:

for (Object key : map.keySet())
    if (something)
        map.remove(key);

抛出了 ConcurrentModificationException,所以我将其更改为:

for (Object key : new ArrayList<Object>(map.keySet()))
    if (something)
        map.remove(key);

这个以及任何其他修改地图的过程都在同步块中。

有更好的解决方案吗?

【问题讨论】:

  • 如果这个方法和其他修改地图的方法在同步块中,我不明白你为什么要做什么?也许我不完全理解你的问题?你能把剩下的代码贴出来吗?
  • @Raedwald,这个问题和它被接受的答案比其他 IMO 更简洁。

标签: java


【解决方案1】:

这是一个在 for 循环中使用迭代器来删除条目的代码示例。

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 上也很好用。
  • 如果在递归删除项目时将项目添加到此 Map,它是否仍会抛出 ConcurrentModificationException?
  • @KPD 如果我们在 for 循环中也有一些其他逻辑,那就更好了,这就是我的情况。
【解决方案2】:

从 Java 8 开始,您可以这样做:

map.entrySet().removeIf(e -> <boolean expression>);

Oracle 文档:entrySet()

集合由地图支持,因此对地图的更改会反映在集合中,反之亦然

【讨论】:

  • 请注意map.values()map.keySet() 也支持removeIf()
  • map.values() 上 removeIf 的行为是什么?它删除了所有指向该值的 key->val 元素?
  • 在 removeIf() 中我们给出条件,它将删除所有匹配的记录
【解决方案3】:

使用真正的迭代器。

Iterator<Object> it = map.keySet().iterator();

while (it.hasNext())
{
  it.next();
  if (something)
    it.remove();
 }

实际上,您可能需要迭代 entrySet() 而不是 keySet() 才能完成这项工作。

【讨论】:

  • 这似乎比我遍历条目集的解决方案更优雅一些。虽然从键集中删除会从地图中删除东西,但它不是很明显(即键集可以是副本)
  • 抱歉重复评论。我已经确认从键集中删除确实会从地图中删除,尽管不如从条目集中删除那么明显。
  • 遍历键集是一种方法。
  • Iterator.next() 或者在这种情况下 it.next() 必须在调用 remove 之前调用,否则将有一个 IllegalStateException
  • 为了记录,这也适用于地图值:Iterator it = map.values().iterator(),然后it.remove() 将删除它的条目。
【解决方案4】:

有更好的解决方案吗?

嗯,在单个语句中肯定有一种更好的方法,但这取决于基于哪些元素被删除的条件。 p>

例如:删除所有 valuetest 的元素,然后在下面使用:

map.values().removeAll(Collections.singleton("test"));

更新 它可以在 Java 8 中使用 Lambda 表达式在一行中完成。

map.entrySet().removeIf(e-> <boolean expression> );

我知道这个问题太老了,但是更新更好的做事方法没有任何害处:)

【讨论】:

  • 这可能是一个老问题,但这确实帮助了我。我从来没有听说过 Collections.singleton() 并且我不知道你可以通过在 values() 上调用 removeAll() 来从地图中删除元素!谢谢。
  • 移除key为test的元素:map.keySet().removeAll(Collections.singleton("test"));
  • @MarouaneLakhal 要删除 key 为 test 的元素,你不只是做 map.remove("test"); ?
  • @dzeikei 如问题所述,map.remove(key) 在遍历 map.keySet() 时抛出了 ConcurrentModificationException。
  • @MarouaneLakhal 如果您已经知道要删除的地图元素的键,那么为什么首先要循环?地图中只能有一个具有相同键的条目。
【解决方案5】:

ConcurrentHashMap

您可以使用java.util.concurrent.ConcurrentHashMap

它实现了ConcurrentMap(它扩展了Map 接口)。

例如

Map<Object, Content> map = new ConcurrentHashMap<Object, Content>();

for (Object key : map.keySet()) {
    if (something) {
        map.remove(key);
    }
}

这种方法不会影响您的代码。只有map 类型不同。

【讨论】:

  • 这种方法的问题是 ConcurrentHashMap 不允许“null”作为值(或键),所以如果你有机会处理包含 null 的映射,你不能使用它接近。
【解决方案6】:

Java 8 支持一种更具声明性的迭代方法,因为我们指定了我们想要的结果,而不是如何计算它。新方法的好处是可读性更高,更不容易出错。

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 ); 
    });
}

结果如下:

Key: 1   Value: one
Key: 2   Value: two
Key: 3   Value: three
After removing element
Key: 1   Value: one
Key: 2   Value: two

【讨论】:

    【解决方案7】:

    您必须使用Iterator 在遍历地图时安全地移除元素。

    【讨论】:

      【解决方案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 并且每次都执行一次获取。 FindBugs 甚至为此提供了一个检测器:findbugs.sourceforge.net/…
      【解决方案9】:

      另一种更详细的方式

      List<SomeObject> toRemove = new ArrayList<SomeObject>();
      for (SomeObject key: map.keySet()) {
          if (something) {
              toRemove.add(key);
          }
      }
      
      for (SomeObject key: toRemove) {
          map.remove(key);
      }
      

      【讨论】:

        【解决方案10】:

        这应该也可以..

        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.getKey());    
          }
        }
        

        【讨论】:

          【解决方案11】:

          也许您可以遍历地图以查找要删除的键并将它们存储在单独的集合中。然后从地图中删除键的集合。在迭代时修改地图通常是不受欢迎的。如果地图非常大,这个想法可能会受到怀疑。

          【讨论】:

          • 如果你可以使用 for-each,那绝对是更优雅的解决方案
          【解决方案12】:
          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 会处理 concurrentModificationExceptiom
          猜你喜欢
          • 1970-01-01
          • 2021-08-01
          • 2011-04-23
          • 2010-11-09
          相关资源
          最近更新 更多