Java 杂谈

java遍历删除集合元素的三种方法以及ConcurrentMod

2018-09-22  本文已影响0人  MDZZ灭顶之灾12138

JAVA中循环遍历list有三种方式for循环、foreach循环、iterator遍历。

1、for循环遍历list

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

这种方式的问题在于,删除某个元素后,list的大小发生了变化,索引也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。因此,这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。

2、foreach循环

 for(String x:list){
        if (x.equals("del"))
            list.remove(x);
        }
    }

这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错

3、iterator遍历

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

这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法,如果用list的remove方法同样会报上面提到的ConcurrentModificationException错误。

总结:
  (1)循环删除list中特定一个元素的,可以使用三种方式中的任意一种
  (2)循环删除list中多个元素的,应该使用迭代器iterator方式

分析java.util.ConcurrentModificationException,环境:jdk1.8.0
AbstractList是ArrayList的父类,这两个类都有iterator()方法

AbstractList代码片段:

protected transient int modCount = 0;
表示对List的修改次数,每次调用add()方法或者remove()方法就会对modCount进行加1操作

If the value of this field changes unexpectedly, the iterator (or list
iterator) will throw a {@code ConcurrentModificationException} in
response to the {@code next}, {@code remove}, {@code previous},
{@code set} or {@code add} operations.

public Iterator<E> iterator() {
    return new Itr();
}
private class Itr implements Iterator<E> {
    int cursor = 0;//表示下一个要访问的元素的索引
    int lastRet = -1;//表示上一个访问的元素的索引
    int expectedModCount = modCount;//表示对ArrayList修改次数的期望值,它的初始值为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的remove():

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
 
 
private void fastRemove(int index) {
    modCount++;//对集合修改了一次
    int numMoved = size - index - 1;//remove后的数量
    if (numMoved > 0)
        /**
          *the source array.
           *starting position in the source array.
           *the destination array.
           *starting position in the destination data.
           *the number of array elements to be copied.
          */
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // Let gc do its work
}

原因:调用list.remove()方法导致modCount和expectedModCount的值不一致。
使用for-each进行迭代也会出现这种问题。

iterator的remove方法调用的就是list.remove()方法,但是它多了一个操作:
expectedModCount = modCount;
多线程的情况下,使用iterator也会出问题。
建议:使用并发CopyOnWriteArrayList代替ArrayList和Vector。

上一篇下一篇

猜你喜欢

热点阅读