[英]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: 两种选择:
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.
因为如果没有,根本不需要从地图上删除它,你可以改变它。
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=DUE2 = DUE
one=uno一个=乌诺
three=tre3 = 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: 根据您正在做的事情,您可以在迭代时执行以下操作:
Iterator.remove()
method to remove the current entry, or Iterator.remove()
方法删除当前条目,或 Map.Entry.setValue()
method to modify the current entry's value. Map.Entry.setValue()
方法修改当前条目的值。 For other types of change, you may need to: 对于其他类型的更改,您可能需要:
Map
from the entries in the current Map
, or Map
中的电流从输入Map
,或 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: 出于此目的,您应该使用地图公开的集合视图:
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: 还是,一些(可能有用的)建议:
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.