1. 数据结构设计
(1) 节点结构
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.item = element;
this.next = next;
this.prev = prev;
}
}
- 双向链接:每个节点同时记录前驱和后继,支持双向遍历。
- 首尾指针:通过
first
和last
字段快速访问链表两端。
(2) 与 ArrayList 的存储对比
特性 | LinkedList |
ArrayList |
---|---|---|
底层结构 | 双向链表 | 动态数组 |
内存占用 | 更高(每个元素额外存储指针) | 更低(连续内存) |
CPU缓存友好性 | 差(节点分散) | 好(局部性原理) |
2. 核心操作实现
(1) 添加元素
尾部插入(add(E e)
)
public boolean add(E e) {
linkLast(e); // 时间复杂度 O(1)
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) {
first = newNode; // 空链表初始化
} else {
l.next = newNode; // 链接原尾节点
}
size++;
}
- 优势:无需扩容,直接修改指针。
指定位置插入(add(int index, E element)
)
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
linkLast(element); // 尾部插入优化
} else {
linkBefore(element, node(index)); // 需要遍历找到目标节点
}
}
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)(需遍历)
(2) 删除元素
remove(int index)
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index)); // 先找到节点,再解除链接
}
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; // 清除引用
}
if (next == null) {
last = prev; // 删除的是尾节点
} else {
next.prev = prev;
x.next = null;
}
x.item = null; // 帮助GC
size--;
return element;
}
- 关键点:正确处理头尾节点的边界条件。
(3) 访问元素
get(int index)
public E get(int index) {
checkElementIndex(index);
return node(index).item; // 需遍历链表
}
- 性能缺陷:随机访问效率远低于
ArrayList
(O(n) vs O(1))。
3. 迭代器实现
(1) 普通迭代器 (Iterator
)
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();
if (!hasNext()) throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
- 快速失败机制:通过
modCount
检测并发修改。
(2) 双向迭代器 (ListIterator
)
支持向前遍历:
public E previous() {
checkForComodification();
if (!hasPrevious()) throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
4. 线程安全与性能优化
(1) 线程安全问题
- 非线程安全:并发修改会导致数据不一致或迭代器失效。
- 解决方案:
或使用并发容器List<String> syncList = Collections.synchronizedList(new LinkedList<>());
ConcurrentLinkedDeque
。
(2) 设计取舍
操作 | LinkedList |
ArrayList |
---|---|---|
头部插入 | O(1) | O(n)(需移动元素) |
中间插入 | O(n)(查找)+ O(1) | O(n)(移动元素) |
随机访问 | O(n) | O(1) |
内存占用 | 高 | 低 |
5. 应用场景建议
- 使用
LinkedList
的场景:- 频繁在 头部/中部 插入删除(如实现队列/栈)。
- 不需要频繁随机访问。
- 使用
ArrayList
的场景:- 需要快速随机访问。
- 内存敏感型应用。