267. Java 集合 - Java 开发必看:ArrayList 与 LinkedList 的全方位对比及选择建议
✅ 总体结论
在绝大多数经典的 List 操作中,ArrayList 是更好的选择 。它在性能和内存使用方面通常优于 LinkedList,尤其当你只是需要一个常规的顺序集合时。
🔍 为什么 ArrayList 更优?
1️⃣ 遍历性能
虽然 LinkedList 的插入和删除操作理论上时间复杂度为 O(1) ,但实际性能却被"指针追踪(Pointer Chasing)"拖慢了。
LinkedList是基于双向链表的,每个元素都包裹在Node对象中,包含prev和next指针。- 每次访问特定索引的元素时,需要从头或尾开始遍历节点,非常慢。
📌 示例对比:
java
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 1_000_000; i++) {
arrayList.add(i);
linkedList.add(i);
}
// 随机读取某个元素
int x1 = arrayList.get(500_000); // O(1)
int x2 = linkedList.get(500_000); // O(n) -- 实际性能明显更慢
2️⃣ 插入性能对比
虽然 LinkedList 在中间插入时不需要移动元素,但你仍然需要遍历到目标位置。
ArrayList中间插入:需要调用System.arraycopy()移动元素,时间复杂度 O(n)。LinkedList中间插入:遍历找到目标位置(O(n))+ 修改指针(O(1))。
📌 示例说明:
java
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add(1, "Eve"); // 插入到中间,需要移动元素
🧠 结论:*虽然
ArrayList插入成本较高,但总体上* 仍然优于LinkedList,因为访问目标位置更快。
3️⃣ 扩容机制影响性能
ArrayList 会自动扩容,其代价是一次性分配更大的数组,并将旧元素拷贝过去。
你可以通过构造函数提前设置容量,避免频繁扩容:
📌 推荐做法:
java
List<Integer> list = new ArrayList<>(10_000); // 提前分配,避免多次扩容
🔁 LinkedList 更适合什么场景?
如果你的数据结构本质上是**队列(Queue)*或者*栈(Stack) ,并且只涉及首尾元素的访问,LinkedList 是一个不错的选择。
java
Deque<Integer> deque = new LinkedList<>();
deque.addFirst(1); // 等价于 push
deque.addLast(2); // 等价于 offer
deque.removeFirst(); // 等价于 pop
✅ 更推荐使用:
ArrayDeque,性能更好、内存占用更低
🧠 内存使用分析
1️⃣ 总体内存占用对比(以存储 1_000 个元素为例):
| 实现方式 | 大致内存占用 |
|---|---|
ArrayList |
~4,976 bytes |
LinkedList |
~24,032 bytes |
LinkedList每个节点有额外的对象头和两个引用字段(prev、next)。ArrayList内部是一个普通数组,节省指针和对象头开销。
2️⃣ 少量元素时的差异(比如只存一个元素)
| 实现 | 内存占用 |
|---|---|
ArrayList 默认容量(10) |
~76 bytes |
ArrayList trimToSize 后 |
~44 bytes |
LinkedList |
~56 bytes |
List.of(1)(不可变) |
~26 bytes ✅最省内存 |
📌 示例说明:
java
List<Integer> a1 = new ArrayList<>();
a1.add(1); // 默认内部数组大小为 10,占内存大
List<Integer> a2 = new ArrayList<>(List.of(1)); // 内部数组大小为 1,占用小
List<Integer> a3 = List.of(1); // 不可变列表,最省内存
3️⃣ 内存不会自动释放?
ArrayList 的内部数组在删除元素后不会自动缩小。
java
List<Integer> list = new ArrayList<>(1000);
list.add(1);
list.clear(); // 清空,但内存仍然保留
((ArrayList<Integer>) list).trimToSize(); // 手动释放未用空间
📊 一张图表:不同情况内存占用变化(单位:字节)
| 元素数量 | ArrayList(2) | LinkedList | 默认ArrayList | TrimmedArrayList |
|---|---|---|---|---|
| 0 | 40 | 32 | 40 | 40 |
| 1 | 48 | 56 | 80 | 48 |
| 2 | 48 | 80 | 80 | 48 |
| 5 | 64 | 152 | 80 | 64 |
| 10 | 96 | 272 | 80 | 80 |
✅
ArrayList(2)是一种灵活的做法,在小集合场景中效果很好,直到元素数超过 9。
✅ 总结建议
| 场景 | 推荐使用 | 说明 |
|---|---|---|
快速随机访问(get(i)) |
✅ ArrayList |
O(1) |
| 插入/删除大量元素(尤其中间位置) | ⚠️ ArrayList |
虽然 O(n),仍然好过 LinkedList |
| 队列/栈场景(只用首尾) | ✅ ArrayDeque |
比 LinkedList 更高效 |
| 极度节省内存 + 不可变数据 | ✅ List.of(...) |
最节省内存 |
| 频繁清空并回收内存 | ✅ ArrayList.trimToSize() |
手动释放冗余空间 |
| 小集合、内存受限 | ✅ ArrayList<>(2) |
小而精的结构,性能内存兼顾 |