remove(int index)通过System.arraycopy将index+1起的元素前移一位,参数为System.arraycopy(elementData, index+1, elementData, index, numMoved),其中numMoved=size-index-1。

remove(int index) 触发的 System.arraycopy 是怎么工作的
调用 remove(int index) 时,ArrayList 不是“删掉一个位置”,而是把 index + 1 开始的所有后续元素,整体往前挪一位。这个挪动靠的是 JVM 优化的本地方法 System.arraycopy,它不走 Java 层循环,直接在内存块间做拷贝。
关键参数顺序是:System.arraycopy(src, srcPos, dest, destPos, length)。ArrayList 源码里实际调用是:System.arraycopy(elementData, index + 1, elementData, index, numMoved),其中 numMoved = size - index - 1。
- 删第一个元素(
index = 0):要移动size - 1个元素,开销最大 - 删最后一个元素(
index = size - 1):numMoved = 0,仍会进 if 分支执行一次空拷贝,但耗时可忽略 - 删中间某个元素:移动量取决于它离末尾有多远,越靠后越轻
remove(Object o) 为什么比 remove(int index) 慢得多
remove(Object o) 先得遍历找匹配项——从头开始逐个调用 equals(),最坏情况要查完整个列表,时间复杂度 O(n);找到后再调 fastRemove(index),又触发一次 System.arraycopy。两步加起来,平均开销远高于直接索引删除。
- 如果目标对象在末尾,查找就花了 O(n),移动却只花 O(1)
- 如果列表里有重复元素,它只删第一个匹配的,不会继续往后找
- 传入
null时走单独分支,也是线性扫描,只是用==判等,略快于equals()
在 for 循环里边遍历边 remove(Object o) 的真实代价
这不是“多一次 remove 调用”那么简单,而是让算法退化成 O(n²):外层循环每走一步,内层 remove(Object o) 都要重新从头扫一遍找元素。更糟的是,每次删除还会导致后续元素前移,下标错位,造成漏删或 IndexOutOfBoundsException。
- 正序 for +
list.remove(obj):删完一个,“下一个”元素已经移到当前下标,下次i++就跳过了它 - 增强 for(foreach)+
list.remove(obj):直接抛ConcurrentModificationException,因为迭代器检测到modCount被修改 - 想安全删多个,要么倒序 for(
i--),要么用迭代器的iterator.remove(),二者都避免了下标漂移和并发检查失败
批量删除时 arraycopy 开销是否能合并
不能。哪怕你调 removeAll(Collection>),底层仍是挨个判断、挨个调 fastRemove(),每次删除都独立触发一次 System.arraycopy。没有“批量移动”的原子操作——ArrayList 不提供类似 C++ std::vector::erase 那种区间擦除接口。
真正省 arraycopy 的办法只有一种:先收集所有待删下标(比如用 LinkedHashSet 记住 index),再倒序去删。这样至少保证每次删除后,前面未删元素的下标不变,不会干扰后续删除定位。
注意 removeAll 内部用的是两次遍历 + 标记数组,最后再做一次紧凑复制,但它不是靠减少 arraycopy 次数来优化,而是靠空间换时间避开反复移动。










