LinkedList 是 Java 中基于双向链表实现的线性表结构,支持高效的插入和删除操作,但随机访问性能较低。本文将从核心原理和实现细节展开分析:
一、核心结构
1. 节点(Node)
每个节点包含前驱指针 、后继指针 和数据,形成链式结构:
swift
java
private static class Node<E> {
E item; // 数据
Node<E> next; // 后继节点
Node<E> prev; // 前驱节点
}
2. 链表头尾
- 头指针(first):指向链表第一个节点。
- 尾指针(last):指向链表最后一个节点。
- 大小(size):记录链表当前元素数量。
二、核心操作实现
1. 插入操作
-
头部插入
创建新节点,将新节点的
next
指向原头节点,更新first
指针:inijava void linkFirst(E e) { Node<E> f = first; Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; // 空链表时更新尾指针 else f.prev = newNode; size++; }
-
尾部插入
创建新节点,将原尾节点的
next
指向新节点,更新last
指针:inijava void linkLast(E e) { Node<E> l = last; Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; // 空链表时更新头指针 else l.next = newNode; size++; }
-
中间插入
在指定位置找到前驱和后继节点,调整前后指针:
inijava void linkBefore(E e, Node<E> succ) { Node<E> pred = succ.prev; Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; // 插入到头部 else pred.next = newNode; size++; }
2. 删除操作
-
删除头节点
断开原头节点的
next
指针,更新first
:inijava E unlinkFirst(Node<E> f) { E element = f.item; Node<E> next = f.next; f.item = null; f.next = null; // 帮助GC first = next; if (next == null) last = null; // 链表为空时更新尾指针 else next.prev = null; size--; return element; }
-
删除尾节点
类似头部删除,更新
last
指针。 -
删除中间节点
调整前驱和后继节点的指针:
inijava E unlink(Node<E> x) { Node<E> pred = x.prev; Node<E> next = x.next; if (pred == null) first = next; // 删除的是头节点 else pred.next = next; if (next == null) last = pred; // 删除的是尾节点 else next.prev = pred; x.item = null; // 清理数据 size--; return x.item; }
3. 查找操作
-
按索引查找
根据索引位置选择从头或尾遍历,时间复杂度 O(n) :
inijava Node<E> node(int 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; } }
三、性能分析
操作 | 时间复杂度 | 说明 |
---|---|---|
插入/删除头尾 | O(1) | 直接调整头尾指针 |
插入/删除中间 | O(n) | 需遍历到目标位置 |
随机访问(get) | O(n) | 必须从头或尾遍历链表 |
迭代遍历 | O(n) | 顺序访问所有节点 |
四、线程安全性
-
非线程安全:多线程并发修改会导致数据不一致。
-
替代方案:
- 使用
Collections.synchronizedList(new LinkedList<>())
。 - 使用并发容器如
ConcurrentLinkedQueue
。
- 使用
五、与 ArrayList 对比
特性 | LinkedList | ArrayList |
---|---|---|
底层结构 | 双向链表 | 动态数组 |
内存占用 | 更高(存储指针) | 更低(连续内存) |
插入/删除性能 | O(1)(头尾) | O(n)(需移动元素) |
随机访问性能 | O(n) | O(1) |
扩容机制 | 无需扩容 | 自动扩容(1.5倍) |
六、应用场景
- 频繁插入/删除 :如实现队列(
Queue
)或栈(Deque
)。 - 动态数据管理:数据规模不确定且需要高效增删。
- 实现高级数据结构:LRU缓存、任务调度队列等。
七、实现细节优化
-
双向遍历优化
查找时根据索引位置选择从头或尾遍历(见
node(int index)
方法)。 -
空值支持
允许存储
null
值,节点通过item == null
判断。 -
迭代器设计
提供
ListIterator
支持双向遍历和修改操作:scssjava public ListIterator<E> listIterator(int index) { checkPositionIndex(index); return new ListItr(index); }
-
Fail-Fast 机制
迭代过程中检测结构性修改(通过
modCount
计数器),防止并发修改异常。
八、注意事项
- 避免随机访问
不要用get(index)
遍历链表,应使用迭代器或增强 for 循环。 - 内存开销
每个元素需额外存储两个指针,内存占用高于ArrayList
。 - 对象生命周期
删除节点后及时清理item
和指针(置为null
),避免内存泄漏。
通过双向链表的灵活指针操作,LinkedList 在特定场景下表现出色,但需根据需求权衡其优缺点。理解其实现机制有助于在开发中合理选择数据结构。