一、链表核心概念
1. 链表与数组的对比
特性 | 数组(顺序表) | 链表 |
---|---|---|
存储结构 | 连续内存空间 | 离散节点通过指针链接 |
随机访问 | O(1) | O(n) |
插入/删除 | O(n)(需移动元素) | O(1)(修改指针) |
内存分配 | 静态/动态预分配 | 动态按需分配 |
缓存友好性 | 高(空间局部性) | 低(节点分散) |
2. 链表类型对比
类型 | 指针方向 | 空间开销 | 操作特性 |
---|---|---|---|
单向链表 | 单方向 | 1个指针/节点 | 尾部操作效率低 |
双向链表 | 双向 | 2个指针/节点 | 支持双向遍历 |
循环链表 | 首尾相连 | 同单向/双向 | 适合环形数据处理 |
二、双向链表实现详解
1. 节点结构定义
private static class Node<E> {
E item; // 存储元素
Node<E> prev; // 前驱指针
Node<E> next; // 后继指针
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.prev = prev;
this.next = next;
}
}
2. 类结构框架
public class DoublyLinkedList<E> {
private Node<E> head; // 头节点
private Node<E> tail; // 尾节点
private int size; // 元素数量
// 核心操作方法
public void addFirst(E element) { /*...*/ }
public void addLast(E element) { /*...*/ }
public E remove(int index) { /*...*/ }
// 其他方法...
}
三、核心操作实现
1. 头部插入(O(1))
public void addFirst(E element) {
Node<E> newNode = new Node<>(null, element, head);
if (head != null) {
head.prev = newNode;
} else {
tail = newNode; // 空链表时头尾指向同一节点
}
head = newNode;
size++;
}
2. 指定位置插入(O(n))
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
addLast(element);
} else {
Node<E> succ = node(index);
Node<E> pred = succ.prev;
Node<E> newNode = new Node<>(pred, element, succ);
succ.prev = newNode;
if (pred == null) {
head = newNode;
} else {
pred.next = newNode;
}
size++;
}
}
3. 删除操作(O(1)节点已知 / O(n)按索引)
public E remove(Node<E> node) {
E element = node.item;
Node<E> prev = node.prev;
Node<E> next = node.next;
if (prev == null) {
head = next;
} else {
prev.next = next;
node.prev = null;
}
if (next == null) {
tail = prev;
} else {
next.prev = prev;
node.next = null;
}
node.item = null; // 帮助GC
size--;
return element;
}
四、Java标准库LinkedList分析
1. 继承体系
classDiagram
class LinkedList<E> {
-int size
-Node<E> first
-Node<E> last
+addFirst(E e)
+addLast(E e)
+poll() : E
+push(E e)
+pop() : E
}
LinkedList --|> AbstractSequentialList
LinkedList --|> Deque
LinkedList --|> List
2. 迭代器优化实现
// JDK源码关键片段
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;
ListItr(int index) {
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
if (!hasNext()) throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
五、性能优化策略
1. 快速定位中间节点
// 快慢指针法(JDK的node()方法优化)
Node<E> node(int index) {
if (index < (size >> 1)) { // 前半部分从头开始
Node<E> x = head;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // 后半部分从尾开始
Node<E> x = tail;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2. 批量操作优化
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0) return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = tail;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
head = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
tail = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
return true;
}
六、应用场景与最佳实践
1. 典型使用场景
-
高频插入/删除:如实时日志处理系统
-
实现队列/双端队列:消息队列系统
-
LRU缓存淘汰算法:结合哈希表实现
-
撤销操作栈:支持快速回退操作
2. 开发注意事项
-
避免索引随机访问:
// 反例:O(n)时间复杂度 for (int i = 0; i < list.size(); i++) { process(list.get(i)); } // 正例:使用迭代器(O(1) per iteration) for (Iterator<E> it = list.iterator(); it.hasNext(); ) { process(it.next()); }
-
选择合适实现类:
// 需要线程安全时 List<String> syncList = Collections.synchronizedList(new LinkedList<>()); // 高并发场景使用 ConcurrentLinkedDeque<String> concurrentDeque = new ConcurrentLinkedDeque<>();
-
内存敏感场景慎用:
-
每个节点额外消耗24字节(对象头12B + 前后指针各4B + 数据引用4B)
-
对比ArrayList每个元素仅需4B引用空间
-
七、常见问题解决方案
1. 链表反转算法
// 迭代法
public void reverse() {
Node<E> current = head;
Node<E> prev = null;
while (current != null) {
Node<E> next = current.next;
current.next = prev;
current.prev = next; // 双向链表需维护prev指针
prev = current;
current = next;
}
tail = head;
head = prev;
}
// 递归法
private Node<E> reverseRecursive(Node<E> current) {
if (current == null) return null;
Node<E> next = current.next;
current.next = current.prev;
current.prev = next;
if (next == null) return current;
return reverseRecursive(next);
}
2. 检测环形链表
// 快慢指针法(Floyd判圈算法)
public boolean hasCycle() {
if (head == null) return false;
Node<E> slow = head;
Node<E> fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
八、Java 8+新特性增强
1. Stream API集成
LinkedList<String> list = new LinkedList<>(Arrays.asList("Java", "Python", "C++"));
// 过滤并收集
List<String> filtered = list.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// 并行处理
list.parallelStream()
.map(String::toUpperCase)
.forEach(System.out::println);
2. Lambda表达式优化
// 删除满足条件的元素(线程安全版)
synchronizedList.removeIf(s -> s.startsWith("A"));
// 替换所有元素
list.replaceAll(s -> s + " Language");
九、总结与选型建议
链表优势场景
-
频繁在头尾进行插入/删除操作
-
不需要随机访问,主要使用迭代器遍历
-
需要实现栈、队列、双端队列等数据结构
-
内存充足且元素数量变化较大
标准库选择建议
需求 | 推荐实现 |
---|---|
通用列表 | ArrayList |
高频头尾操作 | LinkedList |
线程安全 | CopyOnWriteArrayList |
高并发写入 | ConcurrentLinkedDeque |
设计启示:链表与数组的本质区别体现了计算机科学中时间与空间的权衡(Time-Space Tradeoff),开发者应根据具体场景选择最合适的数据结构。