LinkedList 源码深度分析(基于 JDK 8)
LinkedList 是 Java 集合框架中基于双向链表 实现的容器类,同时实现了 List 和 Deque 接口,兼具列表(随机访问元素)和双端队列(首尾操作)的特性。其核心优势是首尾 / 中间插入删除效率高 ,缺点是随机访问效率低。本文从源码角度拆解其设计逻辑、核心方法与特性。
一、类结构与核心成员
1. 类定义
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// ... 核心代码
}
-
继承
AbstractSequentialList:简化顺序访问集合的实现(需重写listIterator和size)。 -
实现
List:支持列表的增删改查、迭代等标准操作。 -
实现
Deque:支持双端队列的首尾插入 / 删除 / 查询(如addFirst、pollLast)。 -
实现
Cloneable:支持浅克隆。 -
实现
Serializable:支持序列化。
2. 核心成员变量
// 链表头节点(前驱为 null)
transient Node<E> first;
// 链表尾节点(后继为 null)
transient Node<E> last;
// 链表元素个数(O(1) 访问)
transient int size = 0;
// 序列化版本号
private static final long serialVersionUID = 876323262645176354L;
transient修饰:first、last、size不参与默认序列化(自定义writeObject/readObject优化序列化逻辑)。
3. 内部节点类(双向链表核心)
LinkedList 的底层是双向链表,每个元素以 Node 节点形式存储,节点间通过「前驱 / 后继引用」关联:
private static class Node<E> {
E item; // 节点存储的元素
Node<E> next; // 后继节点引用
Node<E> prev; // 前驱节点引用
// 构造方法:初始化前驱、元素、后继
Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.item = element;
this.next = next;
}
}
双向链表结构示意图:
first last
↓ ↓
[null] ← Node1 ↔ Node2 ↔ Node3 → [null]
二、构造方法
LinkedList 提供 2 个构造方法,核心是初始化链表的 first、last 和 size。
1. 无参构造
创建空链表(first 和 last 均为 null,size=0):
public LinkedList() {}
2. 带集合参数的构造
将传入的 Collection 元素批量添加到链表尾部:
public LinkedList(Collection<? extends E> c) {
this(); // 调用无参构造初始化空链表
addAll(c); // 批量添加元素
}
核心依赖 addAll 方法,其逻辑是:
-
检查集合非空,转换为数组;
-
遍历数组,依次调用
linkLast(添加到尾部); -
更新
size和modCount(快速失败标记)。
三、核心操作方法解析
LinkedList 的核心操作围绕「双向链表的节点增删改查」展开,关键是通过 node(int index) 高效查找节点,再通过 linkXXX/unlinkXXX 维护节点引用。
1. 节点查找:node(int index)(核心辅助方法)
双向链表的随机访问(按索引找节点)需遍历,但 LinkedList 做了优化:判断索引靠近头还是尾,选择从头部或尾部遍历,减少遍历次数(时间复杂度 O (n/2)):
private Node<E> node(int index) {
// 断言索引合法(0 ≤ index < size)
// assert isElementIndex(index);
// 索引在左半部分:从头遍历
if (index < (size >> 1)) { // size >> 1 等价于 size/2(位运算更快)
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // 索引在右半部分:从尾遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2. 添加元素
(1)尾部添加:add(E e) / addLast(E e)
add(E e) 本质是 addLast,直接调用 linkLast 维护尾节点:
public boolean add(E e) {
linkLast(e);
return true;
}
// 核心:将元素添加到链表尾部
void linkLast(E e) {
final Node<E> l = last; // 保存原尾节点
final Node<E> newNode = new Node<>(l, e, null); // 新节点前驱为原尾节点,后继为 null
last = newNode; // 新节点成为新尾节点
if (l == null) // 原链表为空(l 是 null)
first = newNode; // 新节点同时作为头节点
else
l.next = newNode; // 原尾节点的后继指向新节点
size++;
modCount++; // 修改次数+1(快速失败机制)
}
(2)指定索引添加:add(int index, E element)
需先找到索引对应的节点,再插入到该节点前面:
public void add(int index, E element) {
checkPositionIndex(index); // 检查索引合法性(0 ≤ index ≤ size)
if (index == size) // 索引等于 size → 尾部添加
linkLast(element);
else // 插入到 index 对应的节点前面
linkBefore(element, node(index));
}
// 核心:将元素插入到 succ 节点前面
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev; // 保存 succ 的前驱节点
final Node<E> newNode = new Node<>(pred, e, succ); // 新节点前驱=pred,后继=succ
succ.prev = newNode; // succ 的前驱指向新节点
if (pred == null) // pred 是 null → succ 是头节点
first = newNode; // 新节点成为新头节点
else
pred.next = newNode; // pred 的后继指向新节点
size++;
modCount++;
}
(3)头部添加:addFirst(E e)
调用 linkFirst 维护头节点,逻辑与 linkLast 对称:
public void addFirst(E e) {
linkFirst(e);
}
void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
3. 删除元素
(1)删除头节点:remove() / removeFirst()
remove() 本质是 removeFirst,调用 unlinkFirst 维护头节点:
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null) // 链表为空 → 抛异常
throw new NoSuchElementException();
return unlinkFirst(f);
}
// 核心:删除头节点
private E unlinkFirst(Node<E> f) {
final E element = f.item; // 保存头节点元素(返回用)
final Node<E> next = f.next; // 保存头节点的后继
f.item = null; // 置空元素(帮助 GC,避免内存泄漏)
f.next = null; // 置空后继(断开引用)
first = next; // 后继节点成为新头节点
if (next == null) // 后继为 null → 链表为空
last = null;
else
next.prev = null; // 新头节点的前驱置空
size--;
modCount++;
return element;
}
(2)指定索引删除:remove(int index)
先通过 node(index) 找到节点,再调用 unlink 删除:
public E remove(int index) {
checkElementIndex(index); // 检查索引合法性(0 ≤ index < size)
return unlink(node(index));
}
// 核心:删除指定节点 x(通用删除逻辑)
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next; // x 的后继
final Node<E> prev = x.prev; // x 的前驱
// 处理前驱节点
if (prev == null) {
first = next; // x 是头节点 → 后继成为新头
} else {
prev.next = next; // 前驱的后继指向 x 的后继
x.prev = null; // 断开 x 与前驱的引用
}
// 处理后继节点
if (next == null) {
last = prev; // x 是尾节点 → 前驱成为新尾
} else {
next.prev = prev; // 后继的前驱指向 x 的前驱
x.next = null; // 断开 x 与后继的引用
}
x.item = null; // 置空元素(帮助 GC)
size--;
modCount++;
return element;
}
(3)删除指定元素:remove(Object o)
遍历链表找到匹配元素(支持 null),调用 unlink 删除:
public boolean remove(Object o) {
if (o == null) { // 处理 null 元素(equals 不能传 null)
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else { // 处理非 null 元素(用 equals 匹配)
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false; // 未找到元素
}
4. 查找与修改
(1)按索引查找:get(int index)
直接调用 node(index) 获取节点,返回其元素(O (n) 时间复杂度):
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
(2)首尾查找:getFirst() / getLast()
直接返回头 / 尾节点的元素(O (1) 时间复杂度):
public E getFirst() {
final Node<E> f = first;
if (f == null) throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;
if (l == null) throw new NoSuchElementException();
return l.item;
}
(3)修改元素:set(int index, E element)
找到索引对应的节点,替换其 item 并返回旧值:
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element; // 直接替换元素
return oldVal;
}
四、Deque 接口实现(双端队列特性)
LinkedList 实现了 Deque 接口,支持双端队列的常用操作,核心是复用 linkFirst/linkLast/unlinkFirst/unlinkLast 方法,部分方法区别在于「失败时抛异常还是返回 null」:
| 操作类型 | 头部操作(抛异常) | 头部操作(返回 null) | 尾部操作(抛异常) | 尾部操作(返回 null) |
|---|---|---|---|---|
| 添加 | addFirst(E) |
offerFirst(E) |
addLast(E) |
offerLast(E) |
| 删除 | removeFirst() |
pollFirst() |
removeLast() |
pollLast() |
| 查询 | getFirst() |
peekFirst() |
getLast() |
peekLast() |
示例:offerFirst 与 addFirst 的区别:
public boolean offerFirst(E e) {
addFirst(e);
return true; // 永远返回 true,不会抛异常
}
addFirst 在链表满时抛异常(但 LinkedList 无容量限制,实际不会抛),offerFirst 遵循队列规范返回 true。
五、迭代器实现(ListIterator)
LinkedList 重写了 listIterator() 方法,返回 ListItr 内部类,支持双向迭代 和迭代中增删改(链表迭代效率远高于数组):
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned; // 最后返回的节点
private Node<E> next; // 下一个要访问的节点
private int nextIndex; // 下一个节点的索引
private int expectedModCount = modCount; // 预期修改次数(快速失败)
// 初始化:定位到 index 对应的节点
ListItr(int index) {
next = (index == size) ? null : node(index);
nextIndex = index;
}
// 是否有下一个元素
public boolean hasNext() {
return nextIndex < size;
}
// 访问下一个元素
public E next() {
checkForComodification(); // 检查是否并发修改
if (!hasNext()) throw new NoSuchElementException();
lastReturned = next;
next = next.next; // 指针后移
nextIndex++;
return lastReturned.item;
}
// 是否有前一个元素(双向迭代核心)
public boolean hasPrevious() {
return nextIndex > 0;
}
// 访问前一个元素
public E previous() {
checkForComodification();
if (!hasPrevious()) throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
// 迭代中删除元素(安全删除)
public void remove() {
checkForComodification();
if (lastReturned == null) throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned); // 调用核心删除方法
if (next == lastReturned)
next = lastNext; // 调整 next 指针
else
nextIndex--;
lastReturned = null; // 避免重复删除
expectedModCount++; // 更新预期修改次数
}
// 检查并发修改(快速失败)
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
// 其他方法(add、set)省略...
}
-
双向迭代 :通过
previous()方法访问前一个元素,依赖链表的prev引用。 -
快速失败(fast-fail) :迭代器初始化时记录
expectedModCount,每次操作前检查与modCount是否一致,不一致则抛ConcurrentModificationException(避免并发修改导致迭代异常)。
六、克隆与序列化
1. 克隆(浅拷贝)
LinkedList 的 clone() 是浅拷贝:新链表的节点是新创建的,但节点中的 item 引用与原链表一致(若 item 是可变对象,修改会影响双方):
public Object clone() {
LinkedList<E> clone = superClone(); // 调用 Object.clone() 浅拷贝
// 重置克隆链表为初始状态
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// 遍历原链表,将元素添加到克隆链表(新节点存储原元素引用)
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
2. 序列化(自定义优化)
LinkedList 重写了 writeObject 和 readObject,避免序列化 first/last 节点引用(仅序列化元素本身),减少序列化体积:
// 序列化:遍历链表,写入元素(而非节点引用)
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
s.defaultWriteObject(); // 写入非 transient 成员(如 modCount)
s.writeInt(size); // 写入元素个数
// 按顺序写入每个元素
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
// 反序列化:重建链表
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject(); // 读取非 transient 成员
int size = s.readInt(); // 读取元素个数
// 依次读取元素,添加到尾部
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
七、线程安全性
LinkedList 非线程安全:
-
多线程并发修改(如一个线程迭代,另一个线程添加元素)会触发
ConcurrentModificationException(快速失败机制)。 -
解决方案:
-
手动加锁(
synchronized或ReentrantLock); -
使用
Collections.synchronizedList(new LinkedList<>())包装; -
用并发容器
ConcurrentLinkedDeque(无锁实现,高并发场景更优)。
-
八、优缺点与适用场景
优点
-
插入删除高效:首尾操作 O (1),中间操作(已知节点)O (1)(仅需修改节点引用,无需移动元素);
-
无容量限制:链表动态扩容,无需像 ArrayList 那样预留多余空间;
-
双向迭代支持:实现 Deque 接口,适合做队列、栈、双端队列。
缺点
-
随机访问低效:按索引查找元素需遍历链表,时间复杂度 O (n)(ArrayList 是 O (1));
-
额外内存开销 :每个元素需存储
prev/next引用,占用更多内存。
适用场景
-
频繁进行首尾插入 / 删除操作(如实现队列、栈);
-
较少进行随机访问(按索引查询);
-
元素个数不确定(无需预设容量)。
九、与 ArrayList 的核心区别
| 特性 | LinkedList | ArrayList |
|---|---|---|
| 底层实现 | 双向链表 | 动态数组 |
| 随机访问(get (index)) | O(n) | O(1) |
| 首尾插入删除 | O(1) | O (n)(需移动元素 / 扩容) |
| 中间插入删除 | O (n)(查找节点)+ O (1)(修改引用) | O (n)(移动元素) |
| 内存开销 | 存储 prev/next 引用 |
预留数组空间(可能浪费) |
| 线程安全 | 非线程安全 | 非线程安全 |
| 适用场景 | 频繁增删、队列 / 栈 | 频繁查询、随机访问 |
总结
LinkedList 是基于双向链表的「多功能容器」,设计核心是通过 Node 节点维护前驱 / 后继引用,兼顾 List 和 Deque 的特性。其源码亮点在于:
-
双向遍历优化(
node方法); -
首尾操作高效(
linkFirst/linkLast); -
自定义序列化与浅克隆;
-
支持双向迭代与快速失败机制。