LinkedList 就像 一列火车,每节车厢(节点)都连接着前后车厢,可以轻松插入或移除任意位置的车厢。它用双向链表实现,增删快、查询慢,适合频繁修改数据的场景。
一、底层结构:双向链表
每个节点(车厢)包含三部分:
- 数据:当前节点存储的值。
- 前驱指针(
prev):指向前一个节点。 - 后继指针(
next):指向后一个节点。
二、核心操作原理
1. 添加元素(add())
-
尾部追加:直接修改最后一个节点的
next指针,指向新节点。 -
中间插入:找到插入位置,调整前后节点的指针。
示例:在节点A和B之间插入CA.next = C; C.prev = A; C.next = B; B.prev = C; -
源码关键:
void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; }
2. 删除元素(remove())
-
调整指针:将目标节点的前驱和后继直接连接,跳过被删节点。
示例:删除节点BA.next = C; C.prev = A; -
源码关键:
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; return element; }
3. 查询元素(get())
-
遍历链表:从头节点或尾节点开始,逐个移动指针查找(时间复杂度 O(n))。
示例:获取索引为2的元素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)) |
| 中间增删高效(O(1) 找到位置后) | 内存占用高(每个节点多存两个指针) |
| 无需预分配内存(动态扩容) | 缓存不友好(内存不连续) |
四、适用场景
- 频繁增删:如实现队列(
Queue)或栈(Stack)。 - 中间操作多:如批量插入或删除列表中间的数据。
- 内存敏感度低:能接受额外指针的内存开销。
代码示例:用 LinkedList 实现队列
Queue<String> queue = new LinkedList<>();
queue.offer("任务1"); // 入队
String task = queue.poll(); // 出队
五、与 ArrayList 的对比
| 对比项 | LinkedList | ArrayList |
|---|---|---|
| 底层结构 | 双向链表 | 动态数组 |
| 随机访问 | 慢(O(n)) | 快(O(1)) |
| 头部增删 | 快(O(1)) | 慢(O(n),需移动元素) |
| 内存占用 | 高(每个节点多两个指针) | 低(仅需存数据) |
六、总结
LinkedList 设计核心:
- 用双向链表灵活管理数据,无需连续内存。
- 以空间换时间,优化增删操作效率。
口诀:
「链表结构像火车,增删只需改指针
查询得从头到尾找,内存占用多一点
队列栈用链式好,数组随机访问强!」