Java Enumeration vs Iterator

时间:2012-06-04 10:57:16

标签: java

Enumeration不会抛出ConcurrentModificationException,为什么?

见下面的代码。

public static void main(String[] args) {

    Vector<String> v=new Vector<String>();
    v.add("Amit");
    v.add("Raj");
    v.add("Pathak");
    v.add("Sumit");
    v.add("Aron");
    v.add("Trek");

    Enumeration<String> en=v.elements();

    while(en.hasMoreElements())
    {
        String value=(String) en.nextElement();
        System.out.println(value);
        v.remove(value);

    }

}

它只打印:

Amit
Pathak
Aron

为什么会出现这种情况。我们可以说Enumerator是线程安全的吗。

编辑:使用Iterator时会在单线程应用程序中抛出ConcurrentModificationException

public static void main(String[] args) {

    Vector<String> v=new Vector<String>();
    v.add("Amit");
    v.add("Raj");
    v.add("Pathak");
    v.add("Sumit");
    v.add("Aron");
    v.add("Trek");

    Iterator<String> it=v.iterator();
    while(it.hasNext())
    {
        String value=(String) it.next();
        System.out.println(value);
        v.remove(value);
    }
}

请检查。

7 个答案:

答案 0 :(得分:12)

请注意,ConcurrentModificationException与多线程或线程安全意义上的并发性无关。有些集合允许并发修改,有些则不允许。通常,您可以在文档中找到答案。但并发并不意味着不同的线程同时发生。这意味着您可以在迭代时修改集合。

ConcurrentHashMap是一种特殊情况,因为它在迭代时被明确定义为线程安全且可编辑(我认为对于所有线程安全集合都是如此)。

无论如何,只要您使用单个线程来迭代和修改集合,ConcurrentHashMap就是您的问题的错误解决方案。您错误地使用了API。您应该使用Iterator.remove()删除项目。或者,您可以在迭代和修改原始文件之前复制该集合。

编辑:

我不知道任何抛出ConcurrentModificationException的Enumeration。但是,并发修改时的行为可能与您预期的不同。正如您在示例中看到的,枚举会跳过列表中的每个第二个元素。这是因为它的内部索引随着删除而递增。所以这就是:

  • en.nextElement() - 从Vector返回第一个元素,将index增加到1
  • v.remove(value) - 从Vector中移除第一个元素,将所有元素移位
  • en.nextElement() - 返回Vector中的第二个元素,现在是“Pathak”

Iterator的快速失败行为可以保护您免受此类事件的影响,这就是为什么它通常比Enumberation更好。相反,您应该执行以下操作:

Iterator<String> it=v.iterator();
while(it.hasNext())
{
    String value=(String) it.next();
    System.out.println(value);
    it.remove(); // not v.remove(value); !!
}

可替换地:

for(String value : new Vector<String>(v)) // make a copy
{
    String value=(String) it.next();
    System.out.println(value);
    v.remove(value);
}

第一个当然是可取的,因为只要您按照预期使用API​​,您就不需要副本。

答案 1 :(得分:4)

  

枚举不会抛出ConcurrentModificationException,为什么?

因为调用的代码中没有路径会抛出此异常。 编辑:我指的是Vector类提供的实现,而不是一般的Enumeration接口。

  

为什么会出现这种情况。我们可以说Enumerator是线程安全的吗。

从某种意义上说,执行的代码正确同步是线程安全的。但是,我不认为你的循环产量的结果是你想要的结果。

输出的原因是Enumeration对象维护一个计数器,该计数器在每次nextElement()的调用后递增。此计数器不知道您对remove()的调用。

答案 2 :(得分:3)

抛出ConcurrentModificationException的失败快速行为仅针对Iterator实现,因为您可以读入 http://docs.oracle.com/javase/6/docs/api/java/util/Vector.html

答案 3 :(得分:3)

此处的并发修改与线程无关。

此处的并发只是意味着您正在修改集合,而正在迭代它。 (在你的例子中,这发生在同一个线程中。)

在这种情况下,集合的迭代器和枚举可以抛出ConcurrentModificationException但不必。那些确实表现出失败快速行为的人。显然,Vector的枚举并非快速失败。

线程安全显然涉及多个线程。 Vector仅在其操作(如add,get等)同步的意义上是线程安全的。这是为了避免一个线程添加元素时的非确定性行为,同时另一个线程试图删除一个元素。

现在,当一个线程在结构上修改你的集合而另一个线程正在迭代它时,你必须处理线程安全和并发修改问题。在这种情况下,并且可能一般而言,最安全的是不依赖于ConcurrentModificationException被抛出。最好选择适当的集合实现(例如,一个线程安全的实现),并自行避免/禁止并发修改。

某些迭代器允许通过迭代器本身添加/设置/删除元素。如果你真的需要同时修改,这可能是一个很好的选择。

答案 4 :(得分:1)

简短的回答:它是在枚举之后发明的bonus feature,因此枚举器不抛出它的事实并没有提出任何特别的建议。

答案很长:
来自维基百科:

  

JDK 1.2之前的集合实现[...]不包含   集合框架。用于分组Java的标准方法   对象是通过数组,Vector和Hashtable类,   不幸的是,它不容易扩展,并没有实现   标准成员接口。解决可重用的需求   集合数据结构[...]   馆藏框架主要由约书亚设计和开发   Bloch,并在JDK 1.2中引入。

当Bloch团队这样做时,他们认为当一个新的机制在多线程程序中未正确同步它们的集合时发出警报(ConcurrentModificationException)是个好主意。关于这种机制有两个重要的注意事项:1)它不能保证捕获并发错误 - 只有幸运时才会抛出异常。 2)如果您使用单个线程滥用集合(如您的示例中所示),也会抛出异常。

因此,当多个线程访问时,不会抛出ConcurrentModificationException的集合并不意味着它也是线程安全的。

答案 5 :(得分:0)

这取决于你如何获得枚举。请参阅以下示例,它会抛出ConcurrentModificationException

import java.util.*;

public class ConcurrencyTest {  
    public static void main(String[] args) {  

        Vector<String> v=new Vector<String>();
        v.add("Amit");
        v.add("Raj");
        v.add("Pathak");
        v.add("Sumit");
        v.add("Aron");
        v.add("Trek");

        Enumeration<String> en=Collections.enumeration(v);//v.elements(); 

        while(en.hasMoreElements())
        {
            String value=(String) en.nextElement();
            System.out.println(value);
            v.remove(value);
        }              

        System.out.println("************************************");

        Iterator<String> iter = v.iterator();  
           while(iter.hasNext()){  
              System.out.println(iter.next());  
              iter.remove();  
              System.out.println(v.size());  
        }  
    }  
}  

枚举只是一个接口,它的实际行为与实现有关。 Collections.enumeration()调用中的Enumeration在某种程度上包装了迭代器,所以它确实是快速失败的,但是从调用Vector.elements()获得的Enumeration不是。

未失败的快速枚举可能会在未来的未确定时间引入任意,非确定性行为。例如:如果你编写main方法,它将在第一次迭代后抛出java.util.NoSuchElementException。

public static void main(String[] args) {  

        Vector<String> v=new Vector<String>();
        v.add("Amit");
        v.add("Raj");
        v.add("Pathak");

        Enumeration<String> en = v.elements(); //Collections.enumeration(v);

        while(en.hasMoreElements())
        {   
            v.remove(0);
            String value=(String) en.nextElement();
            System.out.println(value);
        }       
    }  

答案 6 :(得分:-1)

删除v.remove(value),一切都会按预期工作

编辑:抱歉,误读那里的问题

这与线程安全无关。你甚至不是多线程的,所以Java没有理由为此抛出异常。

如果您在更改矢量时需要例外,请将其设为不可修改