List删除元素()

来源:互联网 发布:英国皇家芭蕾舞团 知乎 编辑:程序博客网 时间:2024/05/21 13:52

应用中可能会遇到这种情况,需要比较List中的元素是否满足一定的条件,如果满足条件需要把这个元素从List中删除或是在这个元素前添加一个元素等等。
现在有一个ArrayList我要里面放置了几个字母[A, B, C,C ,D, E, F]
现在我要删除所有的C,于是乎我手速飞快的敲下了以下几行代码

    for(int i = 0; i < list.size(); i++){            if("C".equals(list.get(i))){                list.remove(i);            }        }

看下输出

原始list===[A, B, C, C, D, E, F]删除C后的===[A, B, C, D, E, F]

原因是第一次元素为C时i为2,然后第一个C被删除,后边所有元素下标都向前进,于是第二个C的下标也变成了2,但这时i已经变为3了,所以第二个C不会被遍历到,于是就没有被删除。解决方法是每次删除成功i–

如果使用foreach遍历删除也有坑,回报java.util.ConcurrentModificationException

        for (String string : list) {            if("C".equals(string)){                list.remove("C");            }        }

来看一下为什么回报这个异常,对于集合foreach这种遍历方式其实用的就是迭代器遍历,和下面这种写法没有区别

        Iterator<String> it = list.iterator();        while(it.hasNext()){            if("C".equals(it.next())){                list.remove("C");            }        }

来看下这是个什么异常,JDK8,在ArrayList中找到iterator()方法

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

这个方法返回了一个迭代器,

  private class Itr implements Iterator<E> {        int cursor;       // index of next element to return        int lastRet = -1; // index of last element returned; -1 if no such        int expectedModCount = modCount;        public boolean hasNext() {            return cursor != size;        }        @SuppressWarnings("unchecked")        public E next() {            checkForComodification();            int i = cursor;            if (i >= size)                throw new NoSuchElementException();            Object[] elementData = ArrayList.this.elementData;            if (i >= elementData.length)                throw new ConcurrentModificationException();            cursor = i + 1;            return (E) elementData[lastRet = i];        }        public void remove() {            if (lastRet < 0)                throw new IllegalStateException();            checkForComodification();            try {                ArrayList.this.remove(lastRet);                cursor = lastRet;                lastRet = -1;                expectedModCount = modCount;            } catch (IndexOutOfBoundsException ex) {                throw new ConcurrentModificationException();            }        }

Itr类是ArrayList的内部类,类里面有三个属性
cursor 下一个元素下标
lastRet 最近遍历的元素下标
expectedModCount 期待修改次数

初始化期待修改次数为modCount,modCount记录了实际修改次数当调用ArrayList的remove方法时modCount+1

    public E remove(int index) {        rangeCheck(index);        modCount++;        E oldValue = elementData(index);        int numMoved = size - index - 1;        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null; // clear to let GC do its work        return oldValue;    }

因为迭代器每次next()会调用checkForComodification()

        final void checkForComodification() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();        }

因为只修改了modCount所以现在他和expectedModCount不相等,于是抛出异常。
解决这个问题的方法是使用迭代器的remove方法

        Iterator<String> it = list.iterator();        while(it.hasNext()){            if("C".equals(it.next())){                it.remove();            }        }

在上面的迭代器的remove()方法里面有

 expectedModCount = modCount;

同步期望修改次数和实际修改次数,这样下次检查时他俩相等就不会报错。

但是上边的方法在多线程的情况下也会报 java.util.ConcurrentModificationException

        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                Iterator<String> it1 = list.iterator();                while(it1.hasNext()){                    try {                        Thread.sleep(100L);                        if("C".equals(it1.next())){                            it1.remove();                        }                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            }        });        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                Iterator<String> it1 = list.iterator();                while(it1.hasNext()){                    try {                        Thread.sleep(50);                        if("C".equals(it1.next())){                            it1.remove();                        }                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            }        });        thread.start();        thread2.start();

这是为什么呢?因为modCount是AbstractList中的变量,多个迭代器对list进行remove时只把自己的expectedModCount和modCount同步了,但是并没有通知其他的迭代器去同步。结果就导致一个迭代器删除某个元素,modCount值也变了,当另一个迭代器next()检查expectedModCount和modCount不相同,于是报错。那么通过吧ArrayList换成Vector呢?结果是依然报错。

    public void remove() {            if (lastRet == -1)                throw new IllegalStateException();            synchronized (Vector.this) {                checkForComodification();                Vector.this.remove(lastRet);                expectedModCount = modCount;            }            cursor = lastRet;            lastRet = -1;        }

因为Vector在这了上的锁,会发生什么情况呢?当一个迭代器删除元素时别的迭代器都不能去删除这个元素,但是删除完了呢?迭代器只是同步了自己的expectedModCount,别的迭代器和当前的modCount依然不同。

多线程解决的办法是用CopyOnWriteArrayList

———————————-我是分隔线—————————————————————-

同事说可以用Collections的synchronizedList方法包装一下list,试了一些,仍然对于迭代器的操作仍然是线程不安全的。
具体的可以看这篇博客
http://blog.csdn.net/yangzl2008/article/details/39456817

原创粉丝点击