推荐倒序遍历+remove(i),避免索引错乱且移动量最小;大批量删除用双指针原地覆盖;高可读性场景可用stream过滤,但有gc开销。

在 Java 中,直接用 ArrayList.remove() 遍历删除符合条件的元素会导致频繁数组复制(每次删除都向前搬移后续元素),时间复杂度退化为 O(n²)。高效做法是「反向遍历 + 原地覆盖」或「一次扫描收集索引再批量删除」,核心是减少移动次数。
推荐方案:倒序遍历 + remove(i)
从后往前遍历,删除时不影响尚未检查的前面元素的索引,避免“边删边乱序”问题;每次 remove(int index) 仍会复制,但总移动量最小(只搬移被删位置之后、且尚未被删的那些元素)。
示例:删除所有偶数
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i) % 2 == 0) {
list.remove(i);
}
}
- 比正向遍历安全:不会跳过下一个元素
- 比
Iterator.remove()更快(无迭代器对象开销,且 JVM 对remove(int)有优化) - 适合删除比例不高(如
大批量删除(删除比例高):双指针原地覆盖
不调用 remove,用两个指针:一个读(遍历),一个写(保留位置)。遍历一遍,把要保留的元素前移到写指针处,最后一次性截断尾部。完全避免中间复制。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
示例:保留所有奇数
int writeIndex = 0; for (int readIndex = 0; readIndex
- 时间复杂度严格 O(n),移动次数 = 保留元素个数
- 空间 O(1),无额外集合开销
- 特别适合删除 >50% 元素的场景
替代思路:Stream + Collect(语义清晰,注意性能权衡)
Java 8+ 可用流式过滤,代码简洁:
list = list.stream()
.filter(x -> x % 2 != 0)
.collect(Collectors.toCollection(ArrayList::new));
- 优点:不可变语义、线程安全(源 list 不变)、逻辑直观
- 缺点:创建新 ArrayList,老 list 若很大则 GC 压力大;不适合超大数据量实时处理
- 适用:中等数据量、注重可读性与维护性,或原 list 后续不再使用
不推荐:正向遍历 + Iterator.remove() 或 for-each
正向遍历时用 Iterator.remove() 虽安全,但每次删除仍触发数组复制,且迭代器有额外对象和状态开销;for-each 则会抛 ConcurrentModificationException。
错误示例:
// ❌ 运行时报错
for (Integer x : list) {
if (x % 2 == 0) list.remove(x); // 触发 fail-fast
}
- 除非必须用增强 for,否则应避免
- 即使改用
Iterator,性能也不及倒序遍历或双指针
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










