Java - 通过嵌套循环从单个列表中删除避免并发修改异常

问题描述

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

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;
   }

解决方法

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

然而,在这种特殊情况下,发生异常是因为 iterator1 没有被告知 iterator0 刚刚执行的删除操作,在行 iterator0.remove(); 中。当 iterator1 尝试删除 元素时,它发现其列表已更改。

使用两个迭代器遍历同一个列表并不是一个好主意。我认为您可以使用常规 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;
        }
    }
}

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

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

,

我也会改进算法,而不是一直检查一个元素,而是尝试使用索引玩一点,并将第二个循环索引(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(..);
    }
}
,

也许您的问题在从 pairDocList 切换到 pairDocSet 时可以轻松解决。

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