欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ConcurrentModificationException究极源码分析,为什么不能在迭代器遍历中,一边遍历,一边删除?以及解决方案

程序员文章站 2022-04-11 19:32:31
...

问题

我们都知道在使用迭代器遍历集合时,删除集合中的元素会报java.util.ConcurrentModificationException异常,那么这是为什么呢?
先来看看问题代码:

List<String> list = new ArrayList<>();
list.add("古力娜扎");
list.add("李沁");
list.add("琪琪");
System.out.println(list);
for (String s : list) {
    if(s.equals("古力娜扎")){
        list.remove(s);
    }
}
System.out.println(list);

ConcurrentModificationException究极源码分析,为什么不能在迭代器遍历中,一边遍历,一边删除?以及解决方案
可以看到异常信息是在ArrayList的内部类迭代器类Iter中的checkForComodification()方法中被抛出的,并且这个方法在next()方法中被调用,那我们就进去看看

分析源码

在ArrayList中根据自身结构信息封装了一个迭代器Iter,其中的核心代码:

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];
        }
		......
	}

找到了checkForComodification方法,我们进去揭开它的秘密!

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

好像也没有什么神秘的嘛
就是看看modCountexpectedModCount的值是否相等,如果不相等,就抛出并发修改异常。
当我们在遍历的过程中调用remove(Object o)方法的时候,其实就修改了modCount的值,导致modCountexpectedModCount不相等,就触发java.util.ConcurrentModificationException了呀。
ConcurrentModificationException究极源码分析,为什么不能在迭代器遍历中,一边遍历,一边删除?以及解决方案
ConcurrentModificationException究极源码分析,为什么不能在迭代器遍历中,一边遍历,一边删除?以及解决方案
这就完了吗?当然还没有,这个modCount是什么意思呢?
我们也可以在jdk中找到该字段的解释,大致的解释一下:

  • 该字段被用于记录集合结构的修改的次数,包括对集合长度的修改
  • 结构的改变可能导致遍历过程中产生不正确的结果,这个字段是jdk提供的保证在遍历的时候为了避免不确定性而采取的快速失败的措施
  • 该字段可以被迭代器和迭代器的实现类选择使用,如果不希望实现提供块数失败的迭代器,此字段可以忽略

到此为止,大家应该对java.util.ConcurrentModificationException产生的原因很清晰了吧!

那么如何在一边遍历,一边删除元素呢?

还是可以做到了,下面提供一些方式
1.使用for循环
正向循环遍历,重点是要保证角标一致

for(int i = 0;i < list.size();i++){
    String string = list.get(i);
    if(string.equals("古力娜扎")){
        list.remove(i);
        //保证删除后的数组角标一致
        i = i - 1;
    }
}

逆向循环遍历

for(int i = list.size() - 1;i >= 0;i--){
    String string = list.get(i);
    if(string.equals("古力娜扎")){
        list.remove(i);
    }
}

2.使用迭代器提供的remove方法

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(4);
list.add(3);
System.out.println(list);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
    Integer integer = iterator.next();
    if(integer==2){
        iterator.remove();
    }
}
System.out.println(list);

3.jdk1.8后的removeif()方法

list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.equals("李沁");
    }
});
//lomda表达式
list.removeIf(arg -> arg.equals("琪琪"));

总结:

  • 不是在集合的遍历过程中不能删除元素,而是你的方法不对导致的导致modCountexpectedModCount不相等,从而触发java.util.ConcurrentModificationException
  • 在集合的遍历过程中,修改集合的结构确实容易产生不被预期的结果,因此我们尽量避免这样的操作,或者通过特别的手段保证他的结果不会出乎预期