在 Java 集合框架中,LinkedList 是 List 接口与 Deque 接口的双实现类,底层基于双向链表实现,凭借高效的插入、删除特性,成为 ArrayList 的核心互补集合。
一、LinkedList 基础定义
LinkedList 是java.util包下的双向链表 实现类,同时实现了List和Deque(双端队列)接口,既具备 List 的有序存储特性,又拥有队列、双端队列、栈的功能特性。
核心特性:
- 双向链表结构:每个节点存储数据、前驱节点、后继节点,内存非连续存储;
- 有序可重复:元素存储顺序与插入顺序一致,允许存储 null 值和重复元素;
- 无固定容量:无需扩容,节点动态创建,理论容量无上限;
- 非线程安全:多线程并发修改会引发数据异常;
- 随机访问低效:不支持通过索引直接访问,查找元素需遍历链表。
二、LinkedList 的继承与实现关系
LinkedList 的类定义源码清晰展示了其完整继承体系,这是理解其功能的基础:
java
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

核心继承 / 实现关系解析
- 继承
AbstractSequentialList:抽象父类,专为顺序访问的集合设计(如链表),实现了基于迭代器的通用方法,与 ArrayList 继承的AbstractList形成区分; - 实现
List接口:遵循列表规范,提供增删改查、索引操作等核心功能; - 实现
Deque接口 :核心特性,具备双端队列能力,支持头 / 尾快速增删元素,可作为栈、队列、双端队列使用; - 实现
Cloneable接口:支持浅克隆; - 实现
Serializable接口:支持序列化,可网络传输与持久化。
关键区别:ArrayList 继承
AbstractList支持随机访问,LinkedList 继承AbstractSequentialList仅支持顺序访问。
三、LinkedList 源码解析
LinkedList 的核心是双向链表节点设计 、头尾节点管理 、节点操作逻辑,无扩容机制,源码比 ArrayList 更简洁易懂。
1. 核心成员变量
java
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// 链表元素个数
transient int size = 0;
/**
* 链表头节点(第一个节点)
*/
transient Node<E> first;
/**
* 链表尾节点(最后一个节点)
*/
transient Node<E> last;
// 序列化版本号
private static final long serialVersionUID = 876323262645176354L;
}
2. 核心内部类: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;
}
}
节点结构图解 :prev节点 ← [item, prev, next] → next节点每个节点都能找到前后节点,这是双向链表的核心优势。
3. 构造方法
LinkedList 提供两个极简构造方法,无容量参数(无需扩容):
java
/**
* 无参构造:创建空链表,头尾节点均为null
*/
public LinkedList() {
}
/**
* 集合参数构造:将指定集合元素添加到链表中
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
对比:ArrayList 无参构造会初始化空数组,LinkedList 无参构造直接为空链表,无任何内存分配。
4. 核心节点操作工具方法
LinkedList 所有增删改查,底层都是头节点、尾节点、中间节点的关联操作,核心封装了 4 个工具方法:
(1)linkFirst:将元素添加到链表头部
java
private void linkFirst(E e) {
// 暂存原头节点
final Node<E> f = first;
// 创建新节点:前驱为null,数据为e,后继为原头节点
final Node<E> newNode = new Node<>(null, e, f);
// 新节点成为新头节点
first = newNode;
// 如果原链表为空,新节点也是尾节点
if (f == null)
last = newNode;
else
// 原头节点的前驱指向新节点
f.prev = newNode;
size++;
modCount++;
}
(2)linkLast:将元素添加到链表尾部(默认 add 方法调用)
java
void linkLast(E e) {
// 暂存原尾节点
final Node<E> l = last;
// 创建新节点:前驱为原尾节点,数据为e,后继为null
final Node<E> newNode = new Node<>(l, e, null);
// 新节点成为新尾节点
last = newNode;
// 如果原链表为空,新节点也是头节点
if (l == null)
first = newNode;
else
// 原尾节点的后继指向新节点
l.next = newNode;
size++;
modCount++;
}
(3)linkBefore:在指定节点前插入元素(中间插入)
java
void linkBefore(E e, Node<E> succ) {
// 获取指定节点的前驱节点
final Node<E> pred = succ.prev;
// 创建新节点:前驱=pred,后继=succ
final Node<E> newNode = new Node<>(pred, e, succ);
// 指定节点的前驱指向新节点
succ.prev = newNode;
// 如果前驱为null,新节点为头节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
(4)unlink:删除指定节点(核心删除方法)
java
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 如果是头节点,删除后后继节点成为新头节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null; // 断开前驱引用,帮助GC
}
// 如果是尾节点,删除后前驱节点成为新尾节点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null; // 断开后继引用,帮助GC
}
x.item = null; // 数据置空
size--;
modCount++;
return element;
}
四、LinkedList 核心方法源码解析
基于上述节点工具方法,LinkedList 实现了 List 和 Deque 的所有核心方法,逻辑高度统一。
1. add () 添加元素(最常用)
java
// 默认添加到尾部
public boolean add(E e) {
linkLast(e);
return true;
}
// 指定索引位置添加
public void add(int index, E element) {
checkPositionIndex(index);
// 如果是尾部,直接调用linkLast
if (index == size)
linkLast(element);
else
// 中间插入:找到index对应节点,调用linkBefore
linkBefore(element, node(index));
}
2. node () 查找指定索引的节点(随机访问低效根源)
java
Node<E> node(int index) {
// 二分优化:判断index在前半段还是后半段,减少遍历次数
if (index < (size >> 1)) {
// 前半段:从头节点向后遍历
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;
}
}
优化点:JDK 做了二分遍历优化,但时间复杂度仍为 O (n),远慢于 ArrayList 的 O (1)。
3. get () 获取元素
java
public E get(int index) {
checkElementIndex(index);
// 调用node方法遍历查找节点,返回数据
return node(index).item;
}
4. remove () 删除元素
java
// 根据索引删除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 根据元素删除
public boolean remove(Object o) {
if (o == null) {
// 遍历链表删除null元素
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 遍历链表删除指定元素
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
5. Deque 接口方法(栈 / 队列专用)
LinkedList 可直接作为栈、队列使用,无需额外实现:
java
// 入队/入栈
public void push(E e) { addFirst(e); }
// 出队/出栈
public E pop() { return removeFirst(); }
// 获取队头/栈顶元素
public E peek() { return (first == null) ? null : first.item; }
五、LinkedList 迭代器原理
LinkedList 没有实现RandomAccess接口,只能使用迭代器 / 增强 for 循环遍历(普通 for 循环效率极低),迭代器基于双向链表顺序访问实现。
1. 核心迭代器源码(ListItr)
java
private class ListItr implements ListIterator<E> {
// 上一个返回的节点
private Node<E> lastReturned;
// 下一个要遍历的节点
private Node<E> next;
// 下一个节点索引
private int nextIndex;
// 快速失败标记
private int expectedModCount = modCount;
// 判断是否有下一个元素
public boolean hasNext() {
return nextIndex < size;
}
// 获取下一个元素(顺序遍历)
public E next() {
checkForComodification();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
2. 快速失败机制(Fail-Fast)
与 ArrayList 完全一致:
modCount:链表修改次数(增 / 删都会 + 1);- 迭代器初始化时会拷贝
modCount为expectedModCount; - 迭代过程中如果链表被外部修改,两个值不相等,立即抛出
ConcurrentModificationException。
3. 遍历建议
- 禁止使用普通 for 循环:每次 get (index) 都会从头遍历链表,效率极差;
- 优先使用迭代器 / 增强 for 循环:顺序遍历,时间复杂度 O (n),性能最优。
六、LinkedList 与 ArrayList 对比
两者底层结构、性能、适用场景完全互补:
| 对比维度 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态 Object 数组(内存连续) | 双向链表(内存非连续) |
| 随机访问 | 支持,O (1) 时间复杂度,效率极高 | 不支持,O (n) 时间复杂度,效率低 |
| 插入 / 删除 | 末尾操作 O (1),中间操作 O (n)(需移动元素) | 头 / 尾操作 O (1),中间操作 O (n)(仅修改引用) |
| 扩容机制 | 1.5 倍动态扩容,需数组复制 | 无扩容,节点动态创建 |
| 内存占用 | 内存紧凑,扩容会产生空闲空间 | 每个节点存储数据 + 前后指针,内存开销更大 |
| 遍历方式 | 普通 for / 迭代器均可 | 仅推荐迭代器 / 增强 for 循环 |
| 继承父类 | AbstractList(支持随机访问) | AbstractSequentialList(顺序访问) |
| 实现接口 | List、RandomAccess | List、Deque(双端队列) |
| 线程安全 | 非线程安全 | 非线程安全 |
| 适用场景 | 大量查询、读取操作 | 大量头 / 尾插入、删除操作 |
总结
- 读多写少、频繁随机访问 → ArrayList(开发首选);
- 频繁头尾增删、作为栈 / 队列使用 → LinkedList;
- 两者均非线程安全,多线程环境使用
CopyOnWriteArrayList。
七、LinkedList 面试高频考点
1. LinkedList 的底层结构是什么?
基于双向链表 实现,每个节点包含item(数据)、prev(前驱节点)、next(后继节点)。
2. LinkedList 为什么不需要扩容?
链表无固定容量,新增元素时动态创建 Node 节点,通过引用关联,无需像数组一样重新分配内存。
3. LinkedList 插入 / 删除为什么比 ArrayList 快?
- ArrayList:中间插入 / 删除需要移动后续所有元素,时间复杂度 O (n);
- LinkedList:仅需修改节点的前驱 / 后继引用,无需移动元素,头 / 尾操作时间复杂度 O (1)。
4. LinkedList 为什么随机访问效率低?
不支持索引直接访问,查找元素需要从头 / 尾遍历链表,即使 JDK 做了二分优化,时间复杂度仍为 O (n)。
5. LinkedList 可以作为哪些数据结构使用?
同时实现了 Deque 接口,可作为:List(列表)、栈(Stack)、队列(Queue)、双端队列(Deque)。
6. LinkedList 和 ArrayList 的核心区别?
核心区别是底层结构:数组 vs 双向链表,由此衍生出访问效率、增删效率、内存占用、适用场景的全部差异。
7. LinkedList 遍历为什么不推荐用普通 for 循环?
普通 for 循环每次调用get(index)都会重新遍历链表,时间复杂度 O (n²),性能极差。
8. LinkedList 是线程安全的吗?如何保证安全?
非线程安全;多线程环境可使用Collections.synchronizedList包装,或使用 JUC 包的ConcurrentLinkedQueue(并发链表)。
八、总结
LinkedList 是基于双向链表设计的多功能集合,凭借无扩容、头尾操作高效、支持栈 / 队列特性,成为 ArrayList 的完美互补。
核心记忆点:
- 底层:双向链表,无扩容,节点动态创建;
- 优势:头尾增删极快,支持多种数据结构;
- 劣势:随机访问低效,内存开销大;
- 场景:频繁头尾操作、栈 / 队列需求。