remove(int index) 比 remove(object o) 更快,因其直接索引定位,省去遍历和equals判断;后者需逐个比较,最坏o(n);删除后均通过system.arraycopy移动后续元素,该方法为jvm优化的本地调用。

remove(int index) 为什么比 remove(Object o) 更快
因为 remove(int index) 直接按索引定位,跳过遍历和 equals 判断;而 remove(Object o) 必须从头到尾调用 equals() 找匹配项,最坏 O(n)。两者在删掉元素后,都得移动后续元素——这步靠 System.arraycopy 完成,不是手动 for 循环。
- 移动逻辑统一:从
index + 1开始,拷贝size - index - 1个元素到index起始位置 -
System.arraycopy是 JVM 内置的本地方法,比 Java 层循环快得多,且能触发底层内存块复制优化 - 注意:即使删的是最后一个元素(
index == size - 1),仍会触发一次空移动(拷贝 0 个元素),但开销可忽略
for 循环里调用 remove() 导致 ConcurrentModificationException 或漏删
这是最常踩的坑:用普通 for (int i = 0; i 遍历时调 <code>list.remove(i),会因数组收缩、索引错位导致跳过下一个元素;若用增强 for 或迭代器再调 remove(),则直接抛 ConcurrentModificationException。
- 正确做法是倒序遍历:
for (int i = list.size() - 1; i >= 0; i--) { if (shouldRemove(list.get(i))) list.remove(i); } - 或者用迭代器的
remove()方法:Iterator<string> it = list.iterator(); while (it.hasNext()) { if (shouldRemove(it.next())) it.remove(); }</string> - 别用
list.remove(obj)在循环里反复查——每次都是 O(n),整体变成 O(n²)
System.arraycopy 的参数顺序容易写反
System.arraycopy 的签名是 System.arraycopy(src, srcPos, dest, destPos, length),前两个是源数组和起始位置,后两个是目标数组和起始位置。ArrayList 源码里典型调用是:System.arraycopy(elementData, index+1, elementData, index, numMoved)。
- 常见错误:把
srcPos和destPos搞混,比如写成System.arraycopy(..., index, ..., index+1, ...),结果数据覆盖错位,甚至越界 - 注意:
src和dest可以是同一个数组,但此时必须确保不重叠污染——ArrayList 的移动是「向前覆盖」,所以要求srcPos > destPos,否则行为未定义 - 如果传入
null数组或负索引,会立即抛NullPointerException或ArrayIndexOutOfBoundsException
批量删除时 ArrayList 的扩容/缩容策略不影响 arraycopy 行为
System.arraycopy 只管拷贝数据,不管容量。ArrayList 删除元素后只改 size 字段,不自动缩容;只有显式调 trimToSize() 或下次 add 触发扩容时才可能重建数组。
立即学习“Java免费学习笔记(深入)”;
- 这意味着:删掉一半元素,底层数组长度(
elementData.length)不变,size变小,空间浪费但无性能损失 -
arraycopy每次操作的长度由当前size和删除位置决定,和底层数组长度无关 - 如果频繁增删且内存敏感,要考虑用
LinkedList(但注意随机访问退化)或手动trimToSize()
实际写业务代码时,真正要盯住的不是 System.arraycopy 本身,而是「删的位置是否动态变化」「是否在遍历时修改集合」「参数顺序有没有手滑」——这些地方一错,bug 就藏得深。










