ArrayList-vs-LinkedList

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 的场景

  1. 需要实现队列/双端队列 (但 ArrayDeque 通常表现更好)
  2. 极其频繁的头插头删​(几百上千次并发操作)

🎙 面试回答模板

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》