使用迭代器和iterator.remove()时出现ConcurrentModificationException异常

4
    private int checkLevel(String bigWord, Collection<String> dict, MinMax minMax)
{
    /*value initialised to losing*/
    int value = 0; 
    if (minMax == MinMax.MIN) value = 1; 
    else value = -1; 


    boolean go = true;

    Iterator<String> iter = dict.iterator();

    while(iter.hasNext())
    {
        String str = iter.next(); 
        Collection<Integer> inds = naiveStringSearch(bigWord, str);

        if(inds.isEmpty())
        {
            iter.remove();
        }

        for (Integer i : inds)
        {
            MinMax passin = minMax.MIN;
            if (minMax == MinMax.MIN) passin = minMax.MAX;

            int value2 = checkLevel(removeWord(bigWord, str, i), dict, passin); 
            if (value2 == -1 && minMax == minMax.MIN)
            {
                value = -1; 
                go = false;
            }
            if (value2 == 1 && minMax == minMax.MAX)
            {
                value = 1; 
                go = false; 
            }

        }

        if (go == false) break; 
    }


    return value;
}

错误:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:810)
at java.util.HashMap$KeyIterator.next(HashMap.java:845)
at aStringGame.Main.checkLevel(Main.java:67)
at aStringGame.Main.test(Main.java:117)
at aStringGame.Main.main(Main.java:137)

这里有什么问题?

1
你在 checkLevel 函数中做了什么? - kosa
@Nambari - 我已更新代码以显示整个方法。这是一个递归方法。 - dwjohnston
4个回答

5

某处正在修改dict。我怀疑它可能发生在这个调用内部:

int value2 = checkLevel(removeWord(bigWord, str, i), dict, passin);
                                                     ^^^^

编辑 基本上,发生的情况是对checkLevel()的递归调用通过另一个迭代器修改了dict。这使得外部迭代器的快速失败行为发生。


我已经更新了问题,显示它是一个递归方法。最好的解决方案是什么?克隆我传入的字典吗? - dwjohnston
@jahroy - 但是这个集合是一个哈希集。 (之所以选择哈希集,是因为我关心性能,不确定哈希集在简单迭代和删除元素方面是否更快,但是)。 - dwjohnston
如果您正在迭代,所有集合在性能方面都是相等的。顺便说一下,如果您使用Concurrent Set,您就不会有这个问题。 - Peter Lawrey
使用Set可以通过确保没有重复项来提高性能。如果您从HashSet创建List,则该List也不会有重复项。 - jahroy
如果你担心在Set和List之间进行转换,请查看我的答案的后半部分。 - jahroy

4

当你使用迭代器遍历一个集合时,不能在遍历过程中修改该集合。

调用 iter.remove() 违反了这个规则(你的 removeWord 方法也可能如此)。

如果使用 ListIterator 进行迭代,则可以在遍历 List 时进行修改。

你可以将 Set 转换为 List 并使用 List 迭代器:

List<String> tempList = new ArrayList<String>(dict);
ListIterator li = tempList.listIterator();

另一种选择是在迭代时跟踪要删除的元素。

例如,您可以将它们放入一个 Set 中。

然后您可以在循环后调用 dict.removeAll()

示例:

Set<String> removeSet = new HashSet<String>();
for (String s : dict) {
    if (shouldRemove(s)) {
        removeSet.add(s);
    }
}
dict.removeAll(removeSet);

1

使用 for each 循环时,不允许在循环内部修改正在迭代的 Collection。如果需要修改它,请使用经典的 for 循环。


1
传统的for循环确实可以避免_ConcurrentModificationException_。但是,您无法通过索引访问Set中的元素。 - jahroy

1

这是所有集合类中的常见情况。例如,TreeSet 中的条目使用 failfast 方法。

此类的 iterator 方法返回的迭代器是快速失败的: 如果在创建迭代器之后任何时候以任何方式修改了集合,除非通过迭代器自己的 remove 方法,否则迭代器将抛出 ConcurrentModificationException 异常。因此,在并发修改的情况下,迭代器会快速而干净地失败,而不是在未来的某个不确定时间冒着任意的、不确定性的行为风险。

http://docs.oracle.com/javase/6/docs/api/java/util/TreeSet.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接