Java 迭代地图和更改值时如何避免 ConcurrentModificationException?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4078518/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to avoid ConcurrentModificationException when iterating over a map and changing values?
提问by Jimmy
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?
我的问题是,如果我需要遍历地图并更改值,那么我可以使用哪些最佳实践/方法?要创建一个单独的地图并在我进行时构建它,然后返回副本?
采纳答案by T.J. Crowder
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 referenceto the POJO? E.g., 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.
您要更改对 POJO的引用吗?例如,入口完全指向别的东西?因为如果没有,根本不需要从地图中删除它,您只需更改它即可。
Option 2
选项 2
If you doneed to actually change the reference to the POJO (e.g., the value of the entry), you can still do that in place by iterating over the Map.Entry
instances from entrySet()
. You can use setValue
on the entry, which doesn't modify what you're iterating over.
如果你这样做需要真正改变参考POJO(例如,项的值),你仍然可以做,在地方通过循环Map.Entry
的情况entrySet()
。您可以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
one=uno
three=tre
访问二
修改它
访问一
访问三
二=DUE
一=uno
三=tre
...without any modification exception. You will probably want to synchronize this in case something else is also looking at / mucking with that entry.
...没有任何修改例外。您可能希望同步它,以防其他东西也在查看/破坏该条目。
回答by Stephen C
Iterating over a Map
and adding entries at the same time will result in a ConcurrentModificationException
for most Map
classes. And for the Map
classes that don't (e.g. ConcurrentHashMap
) there is no guarantee that an iteration will visit all entries.
迭代 aMap
并同时添加条目将导致ConcurrentModificationException
大多数Map
类的 a 。对于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 - use the
Map.Entry.setValue()
method to modify the current entry's value.
- 使用该
Iterator.remove()
方法删除当前条目,或 - 使用
Map.Entry.setValue()
方法修改当前条目的值。
For other types of change, you may need to:
对于其他类型的更改,您可能需要:
- create a new
Map
from the entries in the currentMap
, or - build a separate data structure containing changes to be made, then applied to the
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 库具有用于“转换”地图的实用程序类。
回答by mindas
Create a new map (mapNew). Then iterate over the existing map (mapOld), and add all changed and transformed entries into mapNew. After the iteration is complete, put all values from mapNew to mapOld. This might not be good enough if the amount of data is large though.
创建一个新地图(mapNew)。然后迭代现有的地图 (mapOld),并将所有更改和转换的条目添加到 mapNew。迭代完成后,将 mapNew 中的所有值都放到 mapOld 中。如果数据量很大,这可能还不够好。
Or just use Google collections- they have Maps.transformValues()
and Maps.transformEntries()
.
或者只是使用谷歌收藏- 他们有Maps.transformValues()
和Maps.transformEntries()
。
回答by Sean Patrick Floyd
For such purposes you should use the collection views a map exposes:
为此,您应该使用地图公开的集合视图:
- keySet()lets you iterate over keys. That won't help you, as keys are usually immutable.
- values()is what you need if you just want to access the map 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.
- keySet()允许您迭代键。这对您没有帮助,因为键通常是不可变的。
- 如果您只想访问地图值,则values()就是您所需要的。如果它们是可变对象,则可以直接更改,无需将它们放回映射中。
- entrySet()最强大的版本,可以让您直接更改条目的值。
Example: convert the values of all keys that contain an upperscore to uppercase
示例:将包含大写的所有键的值转换为大写
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. I assume your map is of type <String, Object>
:
实际上,如果您只想编辑值对象,请使用 values 集合。我假设您的地图类型为<String, Object>
:
for(Object o: map.values()){
if(o instanceof MyBean){
((Mybean)o).doStuff();
}
}
回答by Buhake Sindi
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.
通常不允许一个线程在另一个线程对其进行迭代时修改集合。
回答by seh
Another approach, somewhat tortured, is to use java.util.concurrent.atomic.AtomicReference
as your map's value type. In your case, that would mean declaring your map of type
另一种有点受折磨的方法是java.util.concurrent.atomic.AtomicReference
用作地图的值类型。在您的情况下,这意味着声明您的地图类型
Map<String, AtomicReference<POJO>>
You certainly don't need the atomicnature 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.Entry
via的廉价方法Map#put()
。
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()
,直到今天我才需要也没有注意到。
回答by Neeme Praks
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-safeand do data updates on POJOs directly. Then you do not need to manipulate the map.
- use 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)
- 使您的 POJO 线程安全并直接在 POJO 上进行数据更新。然后您不需要操作地图。
- 使用ConcurrentHashMap
- 继续使用简单的 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 benchmarkwith realistic data.
哪种方法最好在很大程度上取决于您的应用程序,很难为您提供任何“最佳实践”。与往常一样,使用真实数据制定您自己的基准。