迭代地图并更改值时如何避免ConcurrentModificationException?

我有一个包含一些键(字符串)和值(POJO)的映射

我想迭代这个地图并改变POJO中的一些数据。

我inheritance的当前代码删除了给定的条目,并在对POJO进行一些更改后将其添加回来。

这不能很好地工作,因为在迭代它时你不应该修改地图(方法是同步的,但仍然出现ConcurrentModificationException)

我的问题是 ,如果我需要遍历地图并更改值,那么我可以使用哪些最佳实践/方法? 要创建一个单独的地图并按照我的方式构建,然后返回副本?

两种选择:

选项1

我inheritance的当前代码删除了给定的条目,并在对POJO进行一些更改后将其添加回来。

你是否正在改变对POJO的引用 ? 例如,入口指向其他东西完全? 因为如果没有,根本不需要将其从地图中删除,您只需更改它即可。

选项2

如果确实需要实际更改对POJO的引用(例如,条目的值),您仍然可以通过从entrySet()迭代Map.Entry实例来实现。 您可以在条目上使用setValue ,它不会修改您要迭代的内容。

例:

 Map map; Map.Entry entry; Iterator> it; // Create the map map = new HashMap(); map.put("one", "uno"); map.put("two", "due"); map.put("three", "tre"); // Iterate through the entries, changing one of them it = map.entrySet().iterator(); while (it.hasNext()) { entry = it.next(); System.out.println("Visiting " + entry.getKey()); if (entry.getKey().equals("two")) { System.out.println("Modifying it"); entry.setValue("DUE"); } } // Show the result it = map.entrySet().iterator(); while (it.hasNext()) { entry = it.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } 

输出(无特定顺序)是:

参观两个
修改它
参观一个
参观三
2 = DUE
一个=乌诺
3 = TRE

……没有任何修改例外。 你可能想要同步这个,以防其他人也在查看/删除该条目。

迭代Map并同时添加条目将导致大多数Map类的ConcurrentModificationException 。 对于没有的Map类(例如ConcurrentHashMap ),不能保证迭代将访问所有条目。

根据您正在做什么,您可以在迭代时执行以下操作:

  • 使用Iterator.remove()方法删除当前条目,或
  • 使用Map.Entry.setValue()方法修改当前条目的值。

对于其他类型的更改,您可能需要:

  • 从当前Map的条目创建新Map ,或
  • 构建一个单独的数据结构,包含要进行的更改,然后应用于Map

最后,Google Collections和Apache Commons Collections库具有用于“转换”地图的实用程序类。

出于此目的,您应该使用地图公开的集合视图:

  • keySet()允许您迭代键。 这对你没有帮助,因为键通常是不可变的。
  • 如果您只想访问地图值,则需要values() 。 如果它们是可变对象,您可以直接更改,无需将它们放回到地图中。
  • entrySet()是最强大的版本,允许您更改条目的键和/或值。

示例:将包含upperscore的所有键的值转换为大写

 for(Map.Entry entry:map.entrySet()){ if(entry.getKey().contains("_")) entry.setValue(entry.getValue().toUpperCase()); } 

实际上,如果您只想编辑值对象,请使用values集合进行编辑。 我假设您的地图是类型:

 for(Object o: map.values()){ if(o instanceof MyBean){ ((Mybean)o).doStuff(); } } 

创建一个新地图(mapNew)。 然后遍历现有映射(mapOld),并将所有已更改和已转换的条目添加到mapNew中。 迭代完成后,将mapNew中的所有值都放到mapOld中。 如果数据量很大,这可能不够好。

或者只使用Google集合 – 它们有Maps.transformValues()Maps.transformEntries()

为了提供正确的答案,你应该多解释一下,你想要实现的目标。

还是,一些(可能有用的)建议:

  • 使您的POJO成为线程安全的 ,并直接在POJO上进行数据更新。 然后你不需要操纵地图。
  • 使用ConcurrentHashMap
  • 继续使用简单的HashMap ,但在每个修改上构建一个新的地图并在后台切换地图(同步切换操作或使用AtomicReference )

哪种方法最好取决于您的应用,很难给您任何“最佳实践”。 与往常一样,使用真实数据制作自己的基准

尝试使用ConcurrentHashMap

来自JavaDoc,

一个哈希表,支持检索的完全并发和可更新的预期并发性。

对于ConcurrentModificationException,通常会:

一个线程通常不允许修改Collection而另一个线程迭代它。

另一种有点折磨的方法是使用java.util.concurrent.atomic.AtomicReference作为地图的值类型。 在您的情况下,这将意味着声明您的类型地图

 Map> 

你当然不需要引用的primefaces性质,但它是一种廉价的方法,使值槽可重新绑定,而不必通过Map#put()替换整个Map.Entry

尽管如此,在阅读了其他一些回复之后,我也建议使用Map.Entry#setValue() ,这是我从未需要也没有注意到今天。