ArrayList vs LinkedList ------ 为什么 90% 场景选 ArrayList?
一句话
ArrayList 底层是数组,LinkedList 底层是双向链表。日常开发中 90% 的场景无脑选 ArrayList,LinkedList 只有在频繁头部插入时才发挥优势。
底层结构差异
ArrayList:数组
java
// 伪代码
public class ArrayList<E> {
Object[] elementData; // 底层就是一个数组
int size; // 实际元素个数
}
物理结构:一段连续的内存空间
text
[0] [1] [2] [3] [4] [5] [ ] [ ] [ ] [ ]
↑ ↑
已有数据 预占空位
因为连续存储,CPU 缓存友好------读取一个元素时,相邻的元素也会被预加载到缓存。
LinkedList:双向链表
java
// 伪代码
public class LinkedList<E> {
Node<E> first; // 头指针
Node<E> last; // 尾指针
}
static class Node<E> {
E item; // 数据
Node<E> next; // 指向下一个节点
Node<E> prev; // 指向上一个节点
}
物理结构:分散的内存空间,靠指针连接
text
[NodeA] ⇄ [NodeB] ⇄ [NodeC] ⇄ [NodeD]
↑ ↑
first last
每个节点存了数据和前后两个指针,内存开销大。
核心操作复杂度对比
| 操作 | ArrayList | LinkedList | 胜出 |
|---|---|---|---|
尾部追加 add(e) |
O(1) 均摊(偶尔扩容 O(n)) | O(1)(直接 last.next) | 平手 |
按索引取 get(i) |
O(1) | O(n)(从 first 开始遍历) | ✅ ArrayList |
按索引修改 set(i, v) |
O(1) | O(n)(先找到位置) | ✅ ArrayList |
头部插入 add(0, e) |
O(n)(全部后移) | **O(1)**(改 first 指针) | ✅ LinkedList |
中间插入 add(i, e) |
O(n)(后移) | O(n)(先遍历到 i) | 平手 |
按值查找 contains(e) |
O(n) | O(n) | 平手 |
| 内存占用 | 紧凑(少量预占空位) | 大(每个节点多两个指针) | ✅ ArrayList |
需要纠正的一个常见误解
很多人说 **"LinkedList 插入快"**------这是不完整的。
LinkedList 的 头部插入 确实快(O(1)),但 中间插入 需要先找到位置(O(n)),然后改指针(O(1)),总共还是 O(n)。
text
LinkedList 中间插入:
1 ⇄ 2 ⇄ 3 ⇄ 4 ⇄ 5
↓
1 ⇄ 2 ⇄ 新 ⇄ 3 ⇄ 4 ⇄ 5
步骤:① 遍历 3 次找到位置 ② 改前后指针
总耗时:O(n) + O(1) = O(n)
而很多人以为的"LinkedList 插入 O(1)"其实只说了第二步,忽略了第一步的查找开销。
所以 中间插入两者没本质区别,不要把 LinkedList 当成"插入神器"。
为什么默认是尾插而不是头插?
这是语义问题。List 的契约是按插入顺序排列的。
java
List<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
System.out.println(list); // [A, B, C] ← 先放的先出现
如果默认头插:
java
add("A") → [A]
add("B") → [B, A] ← 顺序反了
add("C") → [C, B, A] ← 跟预期完全不同
所以 LinkedList 也不默认头插,而是默认尾插,维护插入顺序的语义。
而且 LinkedList 内部维护了 last 指针,**尾插也是 O(1)**,不需要遍历。
工程上怎么选?
首选 ArrayList 的场景(覆盖 90% 的情况)
java
// 查数据库返回列表------几乎所有数据展示都是这个模式
List<User> userList = userService.list();
for (User user : userList) { // 遍历
System.out.println(user.getName());
}
User u = userList.get(0); // 随机访问
你在项目里几乎不会遇到必须用 LinkedList 的场景。
因为大部分操作都是:查数据 → 遍历展示 → 偶尔按索引取。
考虑 LinkedList 的场景
- 需要实现队列/双端队列 (但
ArrayDeque通常表现更好) - 极其频繁的头插头删(几百上千次并发操作)
🎙 面试回答模板
text
"ArrayList 底层是动态数组,LinkedList 底层是双向链表。
绝大部分场景直接选 ArrayList:内存紧凑、随机访问 O(1)、CPU 缓存友好。
我项目里所有列表查询都是 ArrayList,查数据库出来的数据要展示和遍历,
按索引随机访问是最常见的操作,ArrayList 天然适合。
LinkedList 只有在频繁头部插入删除且不需要随机访问时才考虑,
实际项目中很少用到这个场景。
不过面试提到时要说清楚:LinkedList 头部插入确实比 ArrayList 快(O(1) vs O(n)),
但中间插入两者都是 O(n)------LinkedList 也要先遍历到位置。"
参考来源
- JDK8 ArrayList、LinkedList 源码
- 《Java 核心技术 卷 I》