老实讲,LinkedList 是我在 Java 集合框架里见过“最被高估、又最被低估”的一个类。说被高估,是因为很多八股文把“增删快、查询慢”讲成了绝对真理,结果真有人拿 LinkedList 做随机访问,线上直接卡到报警;说被低估,是因为它不止是 List,还是一个双向链表、一个 Deque,在特定场景下有很多人没用出来的灵活性。这篇不从背诵结论出发,直接进 JDK 源码、跑几个对照实验,把 LinkedList 从节点结构、增删查改原理、内存开销到真实使用场景完整拆开。无论你是在准备 Java 面试,还是做集合选型,都能从里面找到能直接复用的判断依据。
1. 先看底层:LinkedList 的“双向链表”到底长什么样
1.1 一个 Node 节点,由三部分组成
LinkedList 的源码并不复杂,核心就是一个静态内部类 Node:
java复制private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
每个节点上有三个关键信息:item 存真正的元素,next 指向后一个节点,prev 指向前一个节点。LinkedList 内部再维护两个字段 first 和 last,分别指向链表头和链表尾。
这和 ArrayList 的差异是本质性的。ArrayList 底层是一整块连续内存的 Object[],通过数组下标直接定位;LinkedList 则是一堆散落在堆里的 Node 对象,靠指针串成一条链。第一个 Node 和第二个 Node 在内存里未必挨着,甚至可能隔得很远,这就决定了它无法像数组一样用下标“跳”到某个位置,只能顺着 next 或 prev 一个一个找。
我第一次看这段源码的时候也觉得简单,但后面所有性能问题几乎都能从这个“指针串链”的结构推导出来。不要把 LinkedList 想成“带索引的链表”,它不维护任何索引,所有随机访问都靠遍历。
1.2 同时实现 List 和 Deque,双端能力是先天自带
一个很容易被忽略的事实是,LinkedList 不仅实现了 List 接口,还实现了 Deque 接口,所以它天生支持“双端队列”的语义。也就是说,它既能像列表一样按下标访问,也能像队列一样从头部入队、出队,还能像栈一样 push 和 pop。
看几个常用方法就知道了:
| 语义 | 方法 |
|---|---|
| 头部添加 | addFirst(e) / offerFirst(e) / push(e) |
| 尾部添加 | addLast(e) / offerLast(e) / offer(e) / add(e) |
| 头部移除 | removeFirst() / pollFirst() / pop() / poll() |
| 尾部移除 | removeLast() / pollLast() |
| 查看头部 | peekFirst() / peek() / `element |
