zoukankan      html  css  js  c++  java
  • 对JAVA集合进行遍历删除时务必要用迭代器

    今天同事写了几行类似这样的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String args[]) {
        List<String> famous = new ArrayList<String>();
        famous.add("liudehua");
        famous.add("madehua");
        famous.add("liushishi");
        famous.add("tangwei");
        for (String s : famous) {
            if (s.equals("madehua")) {
                famous.remove(s);
            }
        }
    }

    运行出异常:

    Exception in thread "main" java.util.ConcurrentModificationException

    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

    at java.util.AbstractList$Itr.next(AbstractList.java:343)

    at com.bes.Test.main(Test.java:15)

    Java新手最容易犯的错误,对JAVA集合进行遍历删除时务必要用迭代器。切记。

    其实对于如上for循环,运行过程中还是转换成了如下代码:

    1
    2
    3
    4
    5
    6
    for(Iterator<String> it = famous.iterator();it.hasNext();){
             String s = it.next();
             if(s.equals("madehua")){
                 famous.remove(s);
             }
         }

    仍然采用的是迭代器,但删除操作却用了错误的方法。如将famous.remove(s)改成it.remove()

    则运行正常,结果也无误。

    当然如果改成:

    1
    2
    3
    4
    5
    6
    for (int i = 0; i < famous.size(); i++) {
                String s = famous.get(i);
                if (s.equals("madehua")) {
                    famous.remove(s);
                }
            }

    这种方法,也是可以完成功能,但一般也不这么写。

    为什么用了迭代码器就不能采用famous.remove(s)操作? 这种因为ArrayList与Iterator混合使用时会导致各自的状态出现不一样,最终出现异常。

    我们看一下ArrayList中的Iterator实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    private class Itr implements Iterator<E> {
       /**
        * Index of element to be returned by subsequent call to next.
        */
       int cursor = 0;
       /**
        * Index of element returned by most recent call to next or
        * previous.  Reset to -1 if this element is deleted by a call
        * to remove.
        */
       int lastRet = -1;
       /**
        * The modCount value that the iterator believes that the backing
        * List should have.  If this expectation is violated, the iterator
        * has detected concurrent modification.
        */
       int expectedModCount = modCount;
       public boolean hasNext() {
               return cursor != size();
       }
       public E next() {
               checkForComodification();
           try {
           E next = get(cursor);
           lastRet = cursor++;
           return next;
           catch (IndexOutOfBoundsException e) {
           checkForComodification();
           throw new NoSuchElementException();
           }
       }
       public void remove() {
           if (lastRet == -1)
           throw new IllegalStateException();
               checkForComodification();
           try {
           AbstractList.this.remove(lastRet);
           if (lastRet < cursor)
               cursor--;
           lastRet = -1;
           expectedModCount = modCount;
           catch (IndexOutOfBoundsException e) {
           throw new ConcurrentModificationException();
           }
       }
       final void checkForComodification() {
           if (modCount != expectedModCount)
           throw new ConcurrentModificationException();
       }
       }

    基本上ArrayList采用size属性来维护自已的状态,而Iterator采用cursor来来维护自已的状态。

    当size出现变化时,cursor并不一定能够得到同步,除非这种变化是Iterator主动导致的。

    从上面的代码可以看到当Iterator.remove方法导致ArrayList列表发生变化时,他会更新cursor来同步这一变化。但其他方式导致的ArrayList变化,Iterator是无法感知的。ArrayList自然也不会主动通知Iterator们,那将是一个繁重的工作。Iterator到底还是做了努力:为了防止状态不一致可能引发的无法设想的后果,Iterator会经常做checkForComodification检查,以防有变。如果有变,则以异常抛出,所以就出现了上面的异常。

     如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法(并且在其后使用该迭代器将会有ConcurrentModificationException异常被抛出).

    如果使用迭代器自己的remove方法,那么这个迭代器就仍然是合法的。

    复制代码
    package chapter1;
    
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    
    /**
     * Created by MyWorld on 2016/3/3.
     */
    public class FastFailResolver {
    
        public static void main(String[] args) {
            Map<String, String> source = new HashMap<String, String>();
            for (int i = 0; i < 10; i++) {
                source.put("key" + i, "value" + i);
            }
            System.out.println("Source:" + source);
    //        fastFailSceneWhenRemove(source);
            commonSceneWhenRemove(source);
    
        }
    
        private static void commonSceneWhenRemove(Map<String, String> source) {
            Iterator<Map.Entry<String, String>> iterator = source.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, String> entry = iterator.next();
                if (entry.getKey().contains("1")) {
                    iterator.remove();
                }
            }
            System.out.println(source);
        }
    
        private static void fastFailSceneWhenRemove(Map<String, String> source) {
            for (Map.Entry<String, String> entry : source.entrySet()) {
                if (entry.getKey().contains("1")) {
                    source.remove(entry.getKey());
                }
            }
            System.out.println(source);
        }
    
    
    }
    复制代码

    3.在一个循环中删除一个列表中的元素

    思考下面这一段在循环中删除多个元素的的代码

    1
    2
    3
    4
    5
    ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
    for(int i=0;i<list.size();i++){
        list.remove(i);
    }
    System.out.println(list);

    输出结果是:

    1
    [b,d]

    在这个方法中有一个严重的错误。当一个元素被删除时,列表的大小缩小并且下标变化,所以当你想要在一个循环中用下标删除多个元素的时候,它并不会正常的生效。

    与下面结合的一个示例:

    复制代码
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b",
                    "c", "d"));
            for (int i = 0; i < list.size(); i++) {
                if (list.get(i).equals("a")) {
                    list.remove(i);
                }
            }
            System.out.println(list);
        }
    复制代码

    输出:

    [a, b, c, d]

    即输出与预期不一致


    你也许知道在循环中正确的删除多个元素的方法是使用迭代,并且你知道java中的foreach循环看起来像一个迭代器,但实际上并不是。考虑一下下面的代码:

    1
    2
    3
    4
    5
    6
    ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
    for(String s:list){
        if(s.equals("a")){
            list.remove(s);
        }
    }

    它会抛出一个ConcurrentModificationException异常。 相反下面的显示正常:

    1
    2
    3
    4
    5
    6
    7
    8
    ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
    Iterator<String> iter = list.iterator();
    while(iter.hasNext()){
            String s = iter.next();
            if(s.equals("a")){
                iter.remove();
        }
    }

    .next()必须在.remove()之前调用。在一个foreach循环中,编译器会使.next()在删除元素之后被调用,因此就会抛出ConcurrentModificationException异常,你也许希望看一下ArrayList.iterator()的源代码。

    http://www.cnblogs.com/softidea/p/4279574.html

    复制代码

    import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest{ public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("Test1"); list.add("Test2"); list.add("Test3"); list.add("Test4"); list.add("Test5"); for(Iterator<String> it = list.iterator();it.hasNext();){ if(it.next().equals("Test3")){ it.remove(); } } for(String s : list){ System.out.println(s); } } }
    复制代码

    Iterator 支持从源集合中安全地删除对象,只需在 Iterator 上调用 remove() 即可。这样做的好处是可以避免 ConcurrentModifiedException ,这个异常顾名思意:当打开 Iterator 迭代集合时,同时又在对集合进行修改。
    有些集合不允许在迭代时删除或添加元素,但是调用 Iterator 的remove() 方法是个安全的做法。

    java.util.ConcurrentModificationException详解

    http://blog.csdn.net/smcwwh/article/details/7036663

    【引言】

    经常在迭代集合元素时,会想对集合做修改(add/remove)操作,类似下面这段代码:

    复制代码
    for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
        Integer val = it.next();
        if (val == 5) {
            list.remove(val);
        }
    }
    复制代码

    运行这段代码,会抛出异常java.util.ConcurrentModificationException。

    【解惑】

    (以ArrayList来讲解)在ArrayList中,它的修改操作(add/remove)都会对modCount这个字段+1,modCount可以看作一个版本号,每次集合中的元素被修改后,都会+1(即使溢出)。接下来再看看AbsrtactList中iteraor方法

    public Iterator<E> iterator() {
        return new Itr();
    }

    它返回一个内部类,这个类实现了iterator接口,代码如下:

    复制代码
    private class Itr implements Iterator<E> {
        int cursor = 0;
    
        int lastRet = -1;
    
        int expectedModCount = modCount;
    
        public boolean hasNext() {
            return cursor != size();
        }
    
        public E next() {
            checkForComodification();
            try {
                E next = get(cursor);
                lastRet = cursor++;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
    
        public void remove() {
            if (lastRet == -1)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                // 修改expectedModCount 的值
                expectedModCount = modCount;
                } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
    
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        }
    复制代码

    在内部类Itr中,有一个字段expectedModCount ,初始化时等于modCount,即当我们调用list.iterator()返回迭代器时,该字段被初始化为等于modCount。在类Itr中next/remove方法都有调用checkForComodification()方法,在该方法中检测modCount == expectedModCount,如果不相当则抛出异常ConcurrentModificationException。

    前面说过,在集合的修改操作(add/remove)中,都对modCount进行了+1。
    在看看刚开始提出的那段代码,在迭代过程中,执行list.remove(val),使得modCount+1,当下一次循环时,执行 it.next(),checkForComodification方法发现modCount != expectedModCount,则抛出异常。

    【解决办法】
    如果想要在迭代的过程中,执行删除元素操作怎么办?
    再来看看内部类Itr的remove()方法,在删除元素后,有这么一句expectedModCount = modCount,同步修改expectedModCount 的值。所以,如果需要在使用迭代器迭代时,删除元素,可以使用迭代器提供的remove方法。对于add操作,则在整个迭代器迭代过程中是不允许的。 其他集合(Map/Set)使用迭代器迭代也是一样。

     当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时,即使是在单线程下运行,  java.util.ConcurrentModificationException 异常也将被抛出。   
    Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 
    Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

    所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
    但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

    有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

  • 相关阅读:
    sabaki and leelazero
    apply current folder view to all folders
    string operation in powershell
    wirte function in powershell
    add environment path to powershell
    Module in powershell
    sql prompt
    vmware中鼠标在部分区域不能使用
    调整多个控件的dock的顺序
    行为型模型 策略模式
  • 原文地址:https://www.cnblogs.com/goody9807/p/6432904.html
Copyright © 2011-2022 走看看