java arraylist 在中间插入或删除元素之所以慢,根本原因是其底层基于数组实现,必须通过 system.arraycopy() 将目标位置之后的所有元素整体前移或后移,以填补空缺或腾出空间。这种操作的时间复杂度为 o(n),且涉及内存的批量拷贝,随着列表规模增大,性能损耗会显著增加。

为什么尾部操作快而中间操作慢
尾部操作(如 add(E e))通常只需检查容量并追加元素,时间复杂度为 O(1);而中间操作需要移动大量数据。以下代码演示了两种操作的耗时差异:
import java.util.ArrayList;
import java.util.List;
public class ArrayListPerformance {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 填充100,000个元素
for (int i = 0; i < 100000; i++) {
list.add(i);
}
long startTail = System.nanoTime();
list.add(99999); // 尾部添加
long endTail = System.nanoTime();
System.out.println("Tail add time: " + (endTail - startTail) + " ns");
long startMid = System.nanoTime();
list.add(50000, 0); // 中间插入
long endMid = System.nanoTime();
System.out.println("Mid add time: " + (endMid - startMid) + " ns");
}
}预期结果:尾部添加耗时极短(微秒级),中间插入耗时较长(毫秒级),因为中间插入触发了约50,000次元素位移。

立即学习“Java免费学习笔记(深入)”;
扩容机制如何加剧中间插入的性能开销
当 ArrayList 容量不足时,会触发扩容:创建新数组(通常为原容量的1.5倍),并将旧数组所有元素复制到新数组。若在中间插入导致容量不足,除了常规的元素位移外,还需执行一次全量复制,进一步放大性能损耗。以下代码展示扩容时的行为:
import java.util.ArrayList;
import java.util.List;
public class ArrayListResize {
public static void main(String[] args) {
List<String> list = new ArrayList<>(2); // 初始容量2
list.add("A");
list.add("B");
// 此时容量为2,下次插入将触发扩容
list.add(1, "X"); // 在索引1处插入,需先位移,再可能扩容
System.out.println(list); // [A, X, B]
}
}预期输出:[A, X, B]。该操作涉及:1) 将索引1及之后的元素(即"B")后移一位;2) 若当前容量已满,则扩容并复制所有元素;3) 在空出的位置写入"X"。

频繁中间删除导致的内存碎片与性能下降
频繁在中间删除元素不仅带来 O(n) 的位移开销,还会导致底层数组的有效利用率降低。虽然 ArrayList 不会自动缩小数组容量,但反复删除会使大量元素被“逻辑移除”,实际仍占用内存空间。以下代码验证删除后的状态:
import java.util.ArrayList;
import java.util.List;
public class ArrayListDelete {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
// 删除中间100个元素
for (int i = 450; i < 550; i++) {
list.remove(Integer.valueOf(i));
}
System.out.println("Size: " + list.size()); // 900
System.out.println("Capacity: " + ((ArrayList<Integer>)list).size()); // 注意:无直接获取capacity方法,此处用size模拟逻辑大小
}
}预期输出:Size为900,但底层数组容量仍为1000(或更大,取决于扩容历史)。这意味着有100个槽位为空,造成内存浪费。若需优化,可调用 trimToSize() 释放多余容量,但该操作本身也是 O(n) 的复制过程。


















