集合删除元素及异常ConcurrentModificationException

探讨了在Java中使用ArrayList时,遍历并删除元素可能导致的ConcurrentModificationException异常,分析了异常产生的原因,并提供了四种解决方案,包括逆序遍历、使用CopyOnWriteArrayList、迭代器删除及调整索引。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

集合删除元素及异常ConcurrentModificationException

使用普通for循环正序遍历删除list元素, 会跳过某些数据

public class Demo15 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        for (int i = 0; i < list.size(); i++) {
            System.out.println("i=" + i);
            System.out.println(list.get(i));
            if ("b".equals(list.get(i))){
                list.remove(i);
            }
        }
    }
}

分析:
当删除了元素后, 有些元素遍历不到, 当i=1的时候, 删除b元素, 然后i++所以i变为2, 但是由于b元素删除掉了, 所以c元素的索引变为1, 而d元素的所以变为2, 所以就跳过了c元素
解决方案:
1.当进行删除成功的时候, 再把i–, 类似于迭代器中的指针前移操作
2.使用逆序遍历删除, 如下代码

for (int i = list.size() - 1; i >= 0; i--) {
    System.out.println("i=" + i);
    System.out.println(list.get(i));
    if ("b".equals(list.get(i))){
        list.remove(i);
    }
}

3.使用CopyOnWriteArrayList包装原来的集合后, 使用增强for删除, 不要使用普通for

list = new CopyOnWriteArrayList<>(list);
for (String str : list) {
    System.out.println(str);
    if ("b".equals(str)) {
        list.remove(str);
    }
}

4.使用迭代器删除

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    System.out.println(str);
    if ("b".equals(str)) {
        iterator.remove();
    }
}

这里引出了迭代器, 但注意迭代器容易发生抛异常ConcurrentModificationException的情况, 以下几种会抛异常的场景

场景1, ArrayList迭代器遍历, 但使用集合的remove方法, 发生ConcurrentModificationException

public class Demo15 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        Iterator<String> iterator = list.iterator();
        list.remove(0);
        iterator.next();
    }
}

分析:
为什么使用iterator的遍历, 然后使用ArrayList集合的remove方法会抛异常?
查看ArrayList获取迭代器的源码

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

每调用一次, 返回一个新的ArrayList.Itr()对象, 这是一个内部类, 查看内部类Itr构造方法代码

private class Itr implements Iterator<E> {
    ...
    Itr() {
        this.expectedModCount = ArrayList.this.modCount;
    }
    ...
}

ArrayList成员变量modCount, 为修改次数
内部类Itr成员变量expectedModCount为期望修改次数
构造函数中, 使用ArrayList的modCount值初始化了expectedModCount
查看iterator的remove方法, 有一步checkForComodification()是检查修改次数是否相等的, 要是检查通过了, 则删除集合的一个元素, 并且将expectedModCount同步回modCount, 保持一致

public void remove() {
    if (this.lastRet < 0) {
        throw new IllegalStateException();
    } else {
        this.checkForComodification();

        try {
            ArrayList.this.remove(this.lastRet);
            this.cursor = this.lastRet;
            this.lastRet = -1;
            this.expectedModCount = ArrayList.this.modCount;
        } catch (IndexOutOfBoundsException var2) {
            throw new ConcurrentModificationException();
        }
    }
}

查看checkForComodification方法, modCount和expectedModCount不相等就抛异常了

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

查看ArrayList集合的remove方法

public E remove(int index) {
    Objects.checkIndex(index, this.size);
    Object[] es = this.elementData;
    E oldValue = es[index];
    this.fastRemove(es, index);
    return oldValue;
}

继续查看集合的fastRemove方法, 可以看出对modCount进行++

private void fastRemove(Object[] es, int i) {
    ++this.modCount;
    int newSize;
    if ((newSize = this.size - 1) > i) {
        System.arraycopy(es, i + 1, es, i, newSize - i);
    }

    es[this.size = newSize] = null;
}

结论:
调用ArrayList的remove会对modCount加1导致modCount和初始的expectedModCount不一致, 在checkForComodification检查的时候, 不通过, 抛异常, 其实iterator的next方法也调用, 所以实际上是调用iterator的next方法时检查不过, 抛出的异常

public E next() {
    this.checkForComodification();
    int i = this.cursor;
    if (i >= ArrayList.this.size) {
        throw new NoSuchElementException();
    } else {
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        } else {
            this.cursor = i + 1;
            return elementData[this.lastRet = i];
        }
    }
}

场景2, ArrayList增强for循环中删除元素发生ConcurrentModificationException

public class Demo15 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        for (String str : list) {
            list.remove(str);
        }
    }
}

分析:
集合中的增强for循环会翻译成迭代器(数组中增强for会翻译成普通for), 而在迭代器中又使用了ArrayList集合remove方法, 形结果就场景1一样, 抛异常了(注意: 这里说的是ArrayList, 但是写时复制容器CopyOnWriteArrayList却可以在增强for中使用集合的remove方法删除元素, 这是因为它的迭代器拷贝了原集合的元素, 后面进行详细分析)

场景3, 多线程迭代器发生ConcurrentModificationException
创建两个线程, 对同个集合分别获取迭代器, 进行循环删除元素

public class Demo15 {
    static List<String> list = new ArrayList<>();
    static {
        list.add("a");
        list.add("b");
        list.add("c");
    }

    public static void main(String[] args) {
        new Thread(() -> {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                iterator.next();
                iterator.remove();
            }
        }).start();
        new Thread(() -> {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                iterator.next();
                iterator.remove();
            }
        }).start();
    }
}

分析:
两个线程返回的是不同的迭代器, 每个迭代器都是独立的, 当线程1操作删除后, 更改了list的modCount, 同时线程1返回的迭代器中expectedModCount也更改了, 所以他们是相等的, 但是线程2的expectedModCount还是之前的, 没有做同步修改, 因为只有调用线程2中迭代器的remove才会同步, 所以线程2的迭代器成员变量expectedModCount和list的modCount不一致, 所以在调用线程2迭代器的next方法或者remove方法时, 会去检查修改次数是否一致, 不一致会抛异常ConcurrentModificationException

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值