【问题标题】:Java - Remove from single list over nested loop avoiding concurrent modification exceptionJava - 通过嵌套循环从单个列表中删除,避免并发修改异常
【发布时间】:2021-02-23 08:28:56
【问题描述】:

所以我有这个方法应该在集合中找到对,为此我使用了一个嵌套循环。但是,即使我使用的是迭代器,我总是会遇到并发修改异常。我猜想两个迭代器都迭代同一个集合,他们都试图同时修改它,这就是我得到这个异常的原因。您能否通过完成相同的结果来帮助我避免此错误。

private List<Pair<Document, Document>> createPairDocument(List<Document> documentsToIterate){
       List<Pair<Document, Document>> pairDocList = new ArrayList<>();
       //iterators are used to avoid concurrent modif exception
       Iterator<Document> iterator0 = documents.iterator();
       while(iterator0.hasNext()){
           Document dl0 = iterator0.next();
           Iterator<Document> iterator1 = documents.iterator(); //returns new instance of iterator
           while(iterator1.hasNext()){
               Document dl1 = iterator1.next();
               if (dl1.relatedTo(dl0) && dl0.relatedTo(dl1)){
                   pairDocList.add(Pair.of(dl0, dl1));
                   //these docs should be removed to avoid creating the same relation again
                   iterator0.remove();
                   iterator1.remove();
                   break;
               }
           }
       }
       return pairDocList;
   }

【问题讨论】:

  • 文档可以与自身相关吗?
  • 没有。只对别人。但是我可以在列表中首先拥有文档 A,然后在列表中拥有 B。我首先创建关系 A 到 B,但是当我找到 B 时,我再次创建关系 B - A,这本质上是相同的关系。我尝试只删除 B 而不是两者,但我得到了同样的错误。这可以通过在其位置用空值替换文档并验证在每次迭代中不为空但不是很简单来解决。

标签: java list iterator concurrentmodification


【解决方案1】:

ConcurrentModificationException 的出现是因为迭代器在遍历一个集合时,并不知道该集合被修改了,所以当集合被实际修改时,迭代器变得非常混乱(处于无效状态)。通过使用Iterator.remove 方法,您可以让迭代器知道您正在删除元素,以便迭代器可以相应地调整其状态。

然而,在这种特殊情况下,发生异常是因为iterator1 没有被告知iterator0 刚刚在iterator0.remove(); 行中所做的删除。当iterator1 试图移除 its 元素时,它发现它的列表发生了变化。

使用两个迭代同一列表的迭代器不是一个好主意。我认为您可以使用常规 for 循环来遍历列表的索引,并且每次从该索引 + 1 获取 list 迭代器 ,因为文档不能与自己有关。

for (int i = 0 ; i < documentsToIterate.size() ; i++) {
    var iteratorFromI = documentsToIterate.listIterator(i + 1);
    var dl0 = documentsToIterate.get(i);
    while (iteratorFromI.hasNext()) {
        var dl1 = iteratorFromI.next();
        if (dl1.relatedTo(dl0) && dl0.relatedTo(dl1)){
            pairDocList.add(Pair.of(dl0, dl1));
            iteratorFromI.remove();
            documentsToIterate.remove(i);
            i--; // so that the next one doesn't get skipped
            break;
        }
    }
}

现在我们没有并发修改异常,因为我们在iteratorFromI.remove() 之后执行documentsToIterate.remove(i);,然后我们将迭代器扔掉,所以它永远不知道我们修改了列表:)

或者,只需使用 2 个常规 for 循环。

【讨论】:

    【解决方案2】:

    我还会改进算法,而不是一直检查一个元素,而是尝试对索引进行一些操作,并将第二个循环索引(j)基于第一个(i)的索引。如果您认为列表中可能存在重复项,请不要执行任何删除操作并使用集合。

    for (int i = 0; i < documentsToIterate.size() - 1; i++) {
        for (int j = i + 1; j < documentsToIterate.size(); j++) {
            if (related(doc[i],doc[j]);
               addPair(..);
        }
    }
    

    【讨论】:

    • 喜欢 for-each 循环而不是传统的 for 循环
    • 这里我们需要对索引进行更多的控制,所以我更喜欢做更少的迭代
    【解决方案3】:

    pairDocList 切换到pairDocSet 时,您的问题可能很容易解决。

    当您制作一组 PairDocuments 时,您不需要从列表中删除任何元素。可以将同一 PairDocument add 两次或多次添加到 Set,因为 Set 中没有重复项。您必须努力识别具有正确 equals()hashCode() 的相同 PairDocuments,但这是值得的。

    【讨论】:

    • 是的,这是一个很好的解决方案,但是我将不得不添加另一个条件,即使已经找到了所有对,我仍然必须再次遍历整个集合。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多