list可以一边遍历一边修改元素吗?

List可以一边遍历一边修改元素吗?

📌 核心答案

List能否边遍历边修改,取决于两个因素:

  1. 集合的实现机制(fail-fast 或 fail-safe)
  2. 遍历的方式(增强for、Iterator、索引遍历等)

🔍 详细分析

1. fail-fast集合(ArrayList、LinkedList)

1.1 什么是fail-fast?
  • 定义:快速失败机制,在检测到并发修改时立即抛出异常
  • 实现 :通过modCount计数器检测结构性修改
  • 目的:避免在并发修改时产生不确定的行为
1.2 不同遍历方式的表现
❌ 增强for循环 - 不能修改
arduino 复制代码
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));

// 抛出 ConcurrentModificationException
for (String item : list) {
    if ("B".equals(item)) {
        list.remove(item); // ❌ 异常!
    }
}
⚠️ Iterator遍历 - 有条件修改
vbnet 复制代码
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if ("B".equals(item)) {
        // list.remove(item);  // ❌ 直接修改集合会抛异常
        iterator.remove();     // ✅ 使用Iterator的remove方法
    }
}
✅ 索引遍历 - 可以修改(需注意索引)
csharp 复制代码
// 方式1:正序遍历(需要手动调整索引)
for (int i = 0; i < list.size(); i++) {
    if ("B".equals(list.get(i))) {
        list.remove(i);
        i--; // 重要:调整索引
    }
}

// 方式2:倒序遍历(推荐)
for (int i = list.size() - 1; i >= 0; i--) {
    if ("B".equals(list.get(i))) {
        list.remove(i); // 不需要调整索引
    }
}
❌ Stream forEach - 不能修改
csharp 复制代码
list.stream().forEach(item -> {
    if ("B".equals(item)) {
        list.remove(item); // ❌ ConcurrentModificationException
    }
});
1.3 fail-fast原理解析
csharp 复制代码
public abstract class AbstractList<E> {
    // 修改次数计数器
    protected transient int modCount = 0;
    
    private class Itr implements Iterator<E> {
        // 迭代器创建时记录的modCount
        int expectedModCount = modCount;
        
        public E next() {
            checkForComodification(); // 每次调用都检查
            // ... 返回元素
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        
        public void remove() {
            // ... 执行删除
            expectedModCount = modCount; // 同步modCount
        }
    }
}

2. fail-safe集合(CopyOnWriteArrayList)

2.1 什么是fail-safe?
  • 定义:安全失败机制,允许在遍历时修改集合
  • 实现:遍历的是集合的快照(副本)
  • 特点:修改对当前遍历不可见
2.2 所有遍历方式都可以修改
✅ 增强for循环
csharp 复制代码
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));

for (String item : list) {
    if ("B".equals(item)) {
        list.remove(item); // ✅ 不会抛异常
        list.add("D");     // ✅ 可以添加(但当前遍历看不到)
    }
}
// 遍历看到: A, B, C
// 最终结果: [A, C, D]
✅ Iterator遍历
vbnet 复制代码
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if ("B".equals(item)) {
        list.remove(item);     // ✅ 可以
        // it.remove();         // ⚠️ 注意:不支持Iterator.remove()
    }
}
2.3 fail-safe原理解析
csharp 复制代码
public class CopyOnWriteArrayList<E> {
    private volatile Object[] array;
    
    // 写操作:创建新数组
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            Object[] newElements = new Object[elements.length - 1];
            // ... 复制数组,排除要删除的元素
            setArray(newElements); // 替换引用
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    
    // 读操作:返回快照迭代器
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    
    static final class COWIterator<E> implements Iterator<E> {
        private final Object[] snapshot; // 持有数组快照
        
        private COWIterator(Object[] elements, int initialCursor) {
            snapshot = elements;
        }
    }
}

📊 对比总结表

特性 fail-fast (ArrayList) fail-safe (CopyOnWriteArrayList)
增强for循环修改 ❌ 抛异常 ✅ 可以
Iterator.remove() ✅ 支持 ❌ 不支持
直接修改集合 ❌ 抛异常 ✅ 可以
修改可见性 立即可见 对当前遍历不可见
性能 读写都快 写操作慢(复制数组)
内存占用 高(维护副本)
适用场景 单线程/读写均衡 读多写少的并发场景
相关推荐
M1A117 小时前
小红书重磅升级!公众号文章一键导入,深度内容轻松入驻
后端
0wioiw018 小时前
Go基础(④指针)
开发语言·后端·golang
李姆斯20 小时前
复盘上瘾症:到底什么时候该“复盘”,什么时候不需要“复盘”
前端·后端·团队管理
javachen__20 小时前
Spring Boot配置error日志发送至企业微信
spring boot·后端·企业微信
seabirdssss20 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
OC溥哥9991 天前
Flask论坛与个人中心页面开发教程完整详细版
后端·python·flask·html
迷知悟道1 天前
java面向对象四大核心特征之抽象---超详细(保姆级)
java·后端
Aurora_NeAr1 天前
对比Java学习Go——程序结构与变量
后端
AntBlack1 天前
每周学点 AI:ComfyUI + Modal 的一键部署脚本
人工智能·后端·aigc
5大大大大雄1 天前
docker容器日志处理
后端