为什么在迭代器上调用remove()会产生ConcurrentModificationException?

时间:2012-04-02 03:21:21

标签: java iterator linked-list

我正在尝试编写一个非常简单的方法来删除LinkedList中的重复项:

我尝试在不使用额外缓冲区的情况下执行此操作,因此我在链表上维护两个迭代器,一个执行正常迭代,另一个迭代遍历所有先前节点以检查dupes(如CareerCup中所示);但是,编译器告诉我有一个CME,即使我正在调用itr1.remove():

public static void RemoveWithoutBuffer(LinkedList l) {
    ListIterator itr1 = l.listIterator();   
    int count1 = 0;
    int count2 = 0;
    while (itr1.hasNext()) {
        Object next = itr1.next();
        count1++;
        count2 = 0;
        ListIterator itr2 = l.listIterator();
        while (itr2.hasNext()) {
            count2++;
            if (count2 == count1)
                break;
            if (itr2.next() == next){
                itr1.remove();
            }
        }

    }
}

借助hashset这个问题的另一个更简单的解决方案很简单如下,并且没有异常报告:

    public static void Remove(LinkedList l) {
    HashSet set = new HashSet();
    ListIterator itr = l.listIterator();
    while (itr.hasNext()) {
        Object next = itr.next();
        if (set.contains(next))
            itr.remove();
        else
            set.add(next);
    }
}

是因为当我迭代itr2时我无法修改itr1吗?有没有办法来解决这个问题?谢谢你们。

6 个答案:

答案 0 :(得分:3)

在第一种情况下是 - 你通过iterator2改变集合的内容,而iterator1不知道这些变化。在第二种情况下,HashSet / HashMap在迭代时不允许删除元素。

您可以将删除的元素添加到另一个集合中,并在迭代后删除它们。 E.g。

    List toRemove = new ArrayList();
    for (Object next : collection) {
        if (someCondition) toRemove.add(next);
    }
    collection.removeAll(toRemove);

我希望它有所帮助。

P.S。有关如何从列表中删除元素的更多详细信息,请参阅此处Removing ArrayList object issue

答案 1 :(得分:3)

来自the API docs

  

此类iteratorlistIterator返回的迭代器   方法是失败快速:如果列表在结构上被修改了   创建迭代器之后的时间,除了通过之外的任何方式   Iterator自己的removeadd方法,迭代器将抛出一个   ConcurrentModificationException

答案 2 :(得分:0)

您正在获取CME,因为该列表由第二个迭代器修改,并且第一个迭代器不知道该更改。下次尝试访问列表时,它已被修改。因此它抛出异常。

请一次只使用一个迭代器来修改列表。

答案 3 :(得分:0)

是。您可以这样想:当您创建迭代器时,它会获取列表的当前“修改计数”。当迭代器从列表中删除一个元素时,它会检查修改计数以查看它是否符合预期,如果一切正常,它将删除该元素并更新迭代器和列表上的修改计数。另一个迭代器仍将具有旧的修改计数并查看新值并抛出CME。

在大多数情况下,基于哈希集的方法是正确的解决方案。它会表现得更好 - 两个O(n)通道通常比O(n ^ 2)更好,因为嵌套迭代解决方案会产生(如果它工作)。

答案 4 :(得分:0)

其他人解释了您获得CME的原因,以下是您可以用来删除重复的方法

使用列表toRemove在第一次iterator绊倒时记录元素,之后再次与记录的元素会面时,使用iterator.remove()将其删除

 private void removeDups(List list) {
        List toRemove = new ArrayList();
        for(Iterator  it = list.iterator(); it.hasNext();) {
            Object next = it.next();
            if(!toRemove.contains(next)) {
                toRemove.add(next);
            } else {
                it.remove();
            }
        }
        toremove.clear();
   } 

答案 5 :(得分:0)

是的,你可以在不占用额外空间的情况下解决这个问题。

问题来自于一个接一个地执行这两行。

  • itr1.remove();
  • itr2.hasNext();

您可以同时在同一列表中使用两个迭代器。 如果你小心
但是,一旦其中一个迭代器修改了列表(就像1. connect to hosting via SSH 2. install composer 3. in cpanel open Select PHP version and choose 5.4. (I also set phar extension) 4. install laravel: php composer.phar create-project laravel/laravel myproject --prefer-dist 5. copy everything from myproject/public to public_html 6. open public_html/index.php and set: 7. require __DIR__.'/../myproject/bootstrap/autoload.php'; 8. $app = require_once __DIR__.'/../myproject/bootstrap/start.php'; 那样),你就不能再使用另一个迭代器(所以你不能调用itr1.remove())。

解决方案是在itr2.hasNext()之后加break。并更新itr1.remove()

count1

更优雅的解决方案是比较当前元素之后的元素而不是当前元素之前的元素:

public static void RemoveWithoutBuffer(LinkedList l) {
    ListIterator itr1 = l.listIterator();   
    int count1 = 0;
    int count2 = 0;
    while (itr1.hasNext()) {
        Object next = itr1.next();
        count1++;
        count2 = 0;
        ListIterator itr2 = l.listIterator();
        while (itr2.hasNext()) {
            count2++;
            if (count2 == count1)
                break;
            if (itr2.next() == next){
                itr1.remove();
                --count1;
                break;
            }
        }
    }
}  

就计算复杂性而言,这些并不是最佳解决方案。如果内存空间不是问题,那么关于计算复杂性,hashset解决方案就更好了。

但问题是关于通过迭代器进行并发处理而不是关于复杂性优化。