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() ✅ 支持 ❌ 不支持
直接修改集合 ❌ 抛异常 ✅ 可以
修改可见性 立即可见 对当前遍历不可见
性能 读写都快 写操作慢(复制数组)
内存占用 高(维护副本)
适用场景 单线程/读写均衡 读多写少的并发场景
相关推荐
货拉拉技术27 分钟前
XXL-JOB参数错乱根因剖析:InheritableThreadLocal在多线程下的隐藏危机
java·分布式·后端
桃源学社(接毕设)35 分钟前
基于Django珠宝购物系统设计与实现(LW+源码+讲解+部署)
人工智能·后端·python·django·毕业设计
鹿导的通天塔37 分钟前
高级RAG 00:检索增强生成(RAG)简介
人工智能·后端
xuejianxinokok1 小时前
解惑rust中的 Send/Sync(译)
后端·rust
Siler1 小时前
Oracle利用数据泵进行数据迁移
后端
用户6757049885021 小时前
3分钟,手摸手教你用OpenResty搭建高性能隧道代理(附完整配置!)
后端
coding随想2 小时前
网络世界的“快递站”:深入浅出OSI七层模型
后端·网络协议
skeletron20112 小时前
🚀AI评测这么玩(2)——使用开源评测引擎eval-engine实现问答相似度评估
前端·后端
shark_chili2 小时前
颠覆认知!这才是synchronized最硬核的打开方式
后端
就是帅我不改2 小时前
99%的Java程序员都写错了!高并发下你的Service层正在拖垮整个系统!
后端·架构