当前位置: 首页 > 知识库问答 >
问题:

Java迭代器构造的并发地图修改

谢洛城
2023-03-14

比如说,我做了如下事情:

for (X x : some_map.values ())
    doSomething (x);

其中doSomething()通过几层代码间接地为一些映射添加更多值。使用迭代器(如上面的示例代码),我的脸上会出现ConcurrentModificationException

我可以制作一些_mapaLinkedHashMap,即具有可预测的迭代顺序。此外,当一个新项目被添加到它时,它总是在迭代顺序的末尾被添加。换句话说,如果没有以某种方式抛出ConcurrentModificationException,循环只会在最后迭代新添加的项,也就是说,它会工作得非常好。或者,换一种说法,我这里确实有一个并发的修改,但我可以保证它的行为是明确定义的,而不是错误。

问题:在上面的循环中,我是否可以使用与迭代器“类似”的东西来避免并发修改异常?

请注意,由于一些额外的限制,我无法指出添加项时是否知道循环。我也不能把它改成不是地图的东西。这只是一段代码,但有些地图也在其他地方使用,而且是有原因的地图。

编辑:我的问题是,我是否可以迭代(不一定是以标准方式)我添加项的相同映射。很明显,我可以迭代一个副本,在循环后将副本与原始副本进行比较,以找到新的项,迭代这些项,等等。问题是,我能完全避免这种情况吗,因为在我的情况下,唯一的问题是过于急切地抛出ConnettModificationExc0019。回答“不,你不能”比回答“你可以做...相反”更好,因为我可以自己设计一个替代代码。我只是想知道我是否忽略了一些优雅的解决方案。


共有3个答案

黄伟
2023-03-14

迭代索引而不是实际的集合是一个鲜为人知的技巧。可以使用流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...
}
茅曾琪
2023-03-14

这可能有点争议,但如果你不关心并发修改,为什么不使用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循环将迭代,直到到达链接列表的末尾。然后该方法首先检查修改计数。因此,该行动将有效地应用于所有价值观,甚至是新添加的价值观。

胡玉书
2023-03-14

我在迭代列表时使用的一个技巧是,当这种处理涉及删除元素时,使用递减索引访问列表,并在最后删除项目。

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(