比如说,我做了如下事情:
for (X x : some_map.values ())
doSomething (x);
其中doSomething()
通过几层代码间接地为一些映射添加更多值。使用迭代器(如上面的示例代码),我的脸上会出现
ConcurrentModificationException
。
我可以制作
一些_map
aLinkedHashMap
,即具有可预测的迭代顺序。此外,当一个新项目被添加到它时,它总是在迭代顺序的末尾被添加。换句话说,如果没有以某种方式抛出ConcurrentModificationException
,循环只会在最后迭代新添加的项,也就是说,它会工作得非常好。或者,换一种说法,我这里确实有一个并发的修改,但我可以保证它的行为是明确定义的,而不是错误。
问题:在上面的循环中,我是否可以使用与迭代器“类似”的东西来避免并发修改异常?
请注意,由于一些额外的限制,我无法指出添加项时是否知道循环。我也不能把它改成不是地图的东西。这只是一段代码,但有些地图也在其他地方使用,而且是有原因的地图。
编辑:我的问题是,我是否可以迭代(不一定是以标准方式)我添加项的相同映射。很明显,我可以迭代一个副本,在循环后将副本与原始副本进行比较,以找到新的项,迭代这些项,等等。问题是,我能完全避免这种情况吗,因为在我的情况下,唯一的问题是过于急切地抛出
ConnettModificationExc0019
。回答“不,你不能”比回答“你可以做...相反”更好,因为我可以自己设计一个替代代码。我只是想知道我是否忽略了一些优雅的解决方案。
迭代索引而不是实际的集合是一个鲜为人知的技巧。可以使用流API获取第n个元素。我不确定这会有多高效,因为创建了所有中间对象。
LinkedHashMap<String, String> lhm = new LinkedHashMap();
// fill lhm
for ( int idx=0; idx < lhm.size(); idx++ ) {
String val = lhm.values().stream().skip(idx).findFirst().get();
// process val...
}
这可能有点争议,但如果你不关心并发修改,为什么不使用LinkedHashMap
并忽略ConcurrentModificationException
?
基本上:
try {
myMap.values().forEach(this::doSomething);
}
catch (ConcurrentModificationException ignored) {
}
我认为这将适用于LinkedHashMap
,但它显然不是预期用途。但是,您可以实现自己的版本,该版本将用于此类用途。
“正确”的方法是复制要迭代的值或键,然后检查是否添加了内容。大致上:
final Set<K> processedKeys = new HashSet<>();
do {
final Set<K> keysToProcess = new HashSet<>(myMap.keySet());
keysToProcess.removeAll(processedKeys);
keysToProcess.forEach(key -> doSomething(myMap.get(key)));
} while (!keysToProcess.isEmpty());
更新@Doulep和@RealSkeptic-为什么我认为只是忽略myMap.values()中的异常。
请查看
forEach
的代码或values()
在LinkedHashMap
中返回的集合:
public final void forEach(Consumer<? super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
因此,
forEach
迭代链表。如果将值添加到映射中,它们将被添加到列表中,因此for
循环将迭代,直到到达链接列表的末尾。然后该方法首先检查修改计数。因此,该行动将有效地应用于所有价值观,甚至是新添加的价值观。
我在迭代列表时使用的一个技巧是,当这种处理涉及删除元素时,使用递减索引访问列表,并在最后删除项目。
for(int i=myList.size()-1;i>=0;i--) {
Object item = myList.get(i);
if(needsToBeRemoved(item)) {
myList.remove(i);
}
}
这样,您可以在迭代列表时操作列表。这对列表有效,因为除了迭代器之外,您还可以通过索引访问其元素。
如果在处理地图时遵循某种排序,而这种排序在处理地图时没有改变(有序地图),那么也可以在处理地图时应用相同的技术。
如果您想要或需要使用迭代器,那么除了复制信息(使用第二个映射)之外,别无选择
更新:
还可以使用助手迭代器(键列表)。例如。:
public static void main(String[] args) {
Map<Long, String> map = new HashMap<>();
map.put(1L, "Start");
map.put(10L, "End");
// This throws ConcurrentModificationException
// for (Long value : map.keySet()) {
// map.put(value + 1, "Other");
// }
for (Long value : new ArrayList<Long>(map.keySet())) {
// This works ok
map.put(value + 1, "Other");
}
System.out.println(map);
// Prints: {1=Start, 2=Other, 10=End, 11=Other}
}
我对java相当陌生,实际上我正在编写一个键盘记录器,并让它定期写入文件。每当用户按下某个键时,它都会实例化一个NativeKeyEvent,该事件调用“param string()”并将信息作为字符串添加到下面的arraylist中... 然后,在每一个间隔,字符串数组被传递,并被写入下面的TimerTask线程中的文件。 行'str=iterator.next().tostring();‘然后
问题内容: 我在做: 这引发了ConcurrentModificationException,所以我将其更改为: 此操作以及任何其他修改映射的过程都在同步块中。 有更好的解决方案吗? 如果没有人提出更好的解决方案,那么首先要说“没有” 问题答案: 从Java 8开始,你可以执行以下操作:
我有一个json blob,如下所示: 这只是一个在这里发布的例子。在这个例子中,我们看到它提供了一个学校每年发生的所有事件的列表,该列表是一个累积列表,即上一年的事件也会被追加。我们知道的是,每年的活动都将以“会议开始”活动开始。我想通过这件事,以最新的一系列事件结束。请忽略事件名称中的时间戳或年份,它们只是示例,我在现实世界中没有此类信息。我需要的最终结果是: 所以,我想保留从“会话开始”事件
问题内容: 是否有更简化的方法来执行以下操作? 我正在寻找更接近这个的东西。 问题答案: 不,没有,但是受Objective-C NSDictionary类的启发,我写了一种方法来做到这一点:
当我使用temp=iterator.next()时,sort方法会导致并发修改错误。你能帮我解决并发修改错误吗。我给出了整个类的代码,但我只是尝试完成sort方法。事先谢谢你的帮助。 我必须对ArrayList中的所有数组进行排序。
类似于同步 方式的Iterator,这里有很多不同的方法可以迭代和处理一个Stream中的值。有组合器样式的方法,例如map,filter和fold和他们的有错误就早退的表弟try_map,try_filter和try_fold。 不幸,for循环不适用于Streams,但对于命令式代码,while let和next/try_next函数可以这样用: async fn sum_with_next(