简体   繁体   English

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

[英]How to avoid ConcurrentModificationException when iterating over a map and changing values?

I've got a map containing some keys (Strings) and values (POJOs) 我有一个包含一些键(字符串)和值(POJO)的映射

I want to iterate through this map and alter some of the data in the POJO. 我想迭代这个地图并改变POJO中的一些数据。

The current code I've inherited removes the given entry, and adds it back in after making some changes to the POJO. 我继承的当前代码删除了给定的条目,并在对POJO进行一些更改后将其重新添加。

This doesn't work well, since you shouldn't be modifying a map whilst your iterating through it (method is synchronised, but ConcurrentModificationException still appears) 这不能很好地工作,因为在迭代它时你不应该修改地图(方法是同步的,但仍然出现ConcurrentModificationException)

My question is , if I need to iterate over a map and change values, what are the best practices/methods I can use for doing so? 我的问题是 ,如果我需要遍历地图并更改值,我可以使用哪些最佳实践/方法? To create a separate map and build that up as I go, then return the copy? 要创建一个单独的地图并按照我的方式构建,然后返回副本?

Two options: 两种选择:

Option 1 选项1

The current code I've inherited removes the given entry, and adds it back in after making some changes to the POJO. 我继承的当前代码删除了给定的条目,并在对POJO进行一些更改后将其重新添加。

Are you changing the reference to the POJO? 您是否正在更改对POJO的引用 Eg, so the entry points to something else entirely? 例如,入口指向其他东西完全? Because if not, there's no need to remove it from the map at all, you can just change it. 因为如果没有,根本不需要从地图上删除它,你可以改变它。

Option 2 选项2

If you do need to actually change the reference to the POJO (eg, the value of the entry), you can still do that in place by iterating over the Map.Entry instances from entrySet() . 如果确实需要实际更改对POJO的引用(例如,条目的值),您仍然可以通过从entrySet()迭代Map.Entry实例来实现。 You can use setValue on the entry, which doesn't modify what you're iterating over. 您可以在条目上使用setValue ,它不会修改您要迭代的内容。

Example: 例:

Map<String,String>                  map;
Map.Entry<String,String>            entry;
Iterator<Map.Entry<String,String>>  it;

// Create the map
map = new HashMap<String,String>();
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());
}

The output (in no particular order) is: 输出(无特定顺序)是:

Visiting two 参观两个
Modifying it 修改它
Visiting one 参观一个
Visiting three 参观三
two=DUE 2 = DUE
one=uno 一个=乌诺
three=tre 3 = TRE

...without any modification exception. ......没有任何修改例外。 You will probably want to synchronize this in case something else is also looking at / mucking with that entry. 你可能想要同步它,以防其他东西也在查看/混淆该条目。

Iterating over a Map and adding entries at the same time will result in a ConcurrentModificationException for most Map classes. 迭代Map并同时添加条目将导致大多数Map类的ConcurrentModificationException And for the Map classes that don't (eg ConcurrentHashMap ) there is no guarantee that an iteration will visit all entries. 对于没有的Map类(例如ConcurrentHashMap ),无法保证迭代将访问所有条目。

Depending on exactly what it is you are doing, you may be able to do the following while iterating: 根据您正在做的事情,您可以在迭代时执行以下操作:

  • use the Iterator.remove() method to remove the current entry, or 使用Iterator.remove()方法删除当前条目,或
  • use the Map.Entry.setValue() method to modify the current entry's value. 使用Map.Entry.setValue()方法修改当前条目的值。

For other types of change, you may need to: 对于其他类型的更改,您可能需要:

  • create a new Map from the entries in the current Map , or 创建一个新的Map中的电流从输入Map ,或
  • build a separate data structure containing changes to be made, then applied to the Map . 构建一个单独的数据结构,包含要进行的更改,然后应用于Map

And finally, the Google Collections and Apache Commons Collections libraries have utility classes for "transforming" maps. 最后,Google Collections和Apache Commons Collections库具有用于“转换”地图的实用程序类。

For such purposes you should use the collection views a map exposes: 出于此目的,您应该使用地图公开的集合视图:

  • keySet() lets you iterate over keys. keySet()允许您遍历键。 That won't help you, as keys are usually immutable. 这对你没有帮助,因为键通常是不可变的。
  • values() is what you need if you just want to access the map values. 如果您只想访问地图值,则需要values() If they are mutable objects, you can change directly, no need to put them back into the map. 如果它们是可变对象,您可以直接更改,无需将它们放回到地图中。
  • entrySet() the most powerful version, lets you change an entry's value directly. entrySet()是最强大的版本,允许您直接更改条目的值。

Example: convert the values of all keys that contain an upperscore to uppercase 示例:将包含upperscore的所有键的值转换为大写

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

Actually, if you just want to edit the value objects, do it using the values collection. 实际上,如果您只想编辑值对象,请使用values集合进行编辑。 I assume your map is of type <String, Object> : 我假设您的地图是<String, Object>类型:

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

Create a new map (mapNew). 创建一个新地图(mapNew)。 Then iterate over the existing map (mapOld), and add all changed and transformed entries into mapNew. 然后遍历现有映射(mapOld),并将所有已更改和已转换的条目添加到mapNew中。 After the iteration is complete, put all values from mapNew to mapOld. 迭代完成后,将mapNew中的所有值都放到mapOld中。 This might not be good enough if the amount of data is large though. 如果数据量很大,这可能不够好。

Or just use Google collections - they have Maps.transformValues() and Maps.transformEntries() . 或者只使用Google集合 - 它们有Maps.transformValues()Maps.transformEntries()

In order to provide a proper answer, you should explain a bit more, what you are trying to achieve. 为了提供正确的答案,你应该多解释一下,你想要实现的目标。

Still, some (possibly useful) advice: 还是,一些(可能有用的)建议:

  • make your POJOs thread-safe and do data updates on POJOs directly. 使您的POJO成为线程安全的 ,并直接在POJO上进行数据更新。 Then you do not need to manipulate the map. 然后你不需要操纵地图。
  • use ConcurrentHashMap 使用ConcurrentHashMap
  • keep on using simple HashMap , but build a new map on each modification and switch maps behind the scenes (synchronizing the switch operation or using AtomicReference ) 继续使用简单的HashMap ,但在每个修改上构建一个新的地图并在后台切换地图(同步切换操作或使用AtomicReference

Which approach is best depends heavily on your application, it is difficult to give you any "best practice". 哪种方法最好取决于您的应用程序,很难给您任何“最佳实践”。 As always, make your own benchmark with realistic data. 与往常一样,使用真实数据制作自己的基准

Try using ConcurrentHashMap . 尝试使用ConcurrentHashMap

From JavaDoc, 来自JavaDoc,

A hash table supporting full concurrency of retrievals and adjustable expected concurrency for updates. 一个哈希表,支持检索的完全并发性和可更新的预期并发性。

For ConcurrentModificationException to occur, generally: 对于ConcurrentModificationException,通常会:

it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. 一个线程通常不允许修改Collection而另一个线程迭代它。

Another approach, somewhat tortured, is to use java.util.concurrent.atomic.AtomicReference as your map's value type. 另一种有点折磨的方法是使用java.util.concurrent.atomic.AtomicReference作为地图的值类型。 In your case, that would mean declaring your map of type 在您的情况下,这将意味着声明您的类型地图

Map<String, AtomicReference<POJO>>

You certainly don't need the atomic nature of the reference, but it's a cheap way to make the value slots rebindable without having to replace the entire Map.Entry via Map#put() . 你当然不需要引用的原子性质,但它是一种廉价的方法,使值槽可重新绑定,而不必通过Map#put()替换整个Map.Entry

Still, having read some of the other responses here, I too recommend use of Map.Entry#setValue() , which I had never needed nor noticed until today. 尽管如此,在阅读了其他一些回复之后,我也建议使用Map.Entry#setValue() ,这是我从未需要也没有注意到今天。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 迭代map时出现ConcurrentModificationException - ConcurrentModificationException when iterating over map 如何在迭代 map.entrySet 时避免 ConcurrentModificationException - How to avoid ConcurrentModificationException while iterating over map.entrySet 如何在迭代时仅避免ArrayList中的ConcurrentModificationException? - How do I avoid ConcurrentModificationException in ArrayList ONLY when iterating? 迭代此集合时如何避免ConcurrentModificationException? - How to avoid ConcurrentModificationException while iterating this collection? 迭代并从 ArrayList 中删除元素时如何避免 java.util.ConcurrentModificationException - How to avoid java.util.ConcurrentModificationException when iterating through and removing elements from an ArrayList 在LinkedList上迭代时出现ConcurrentModificationException - ConcurrentModificationException when iterating on LinkedList 迭代Map中的键和值 - Iterating over keys and values in a Map 遍历Map条目时如何避免DuplicateFormatFlagsException - How to avoid DuplicateFormatFlagsException when iterating through Map entries 迭代ArrayList时Java ConcurrentModificationException - Java ConcurrentModificationException when iterating ArrayList 在这种情况下如何避免 ConcurrentModificationException? - How to Avoid ConcurrentModificationException in that case?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM