Node内部类含item、next、prev三个字段,构造时必须同时指定prev和next以维持双向链表链接,空链表时first与last均为null,首尾插入及已知节点删除均为O(1)。

Node内部类长什么样:看懂三个字段怎么串起整条链
LinkedList 的 Node 不是用户要写的类,而是它自己维护链表的“砖块”——每个节点存一个值,并用两个指针记住前后邻居。源码里就这三样:item(数据)、next(指向后一个 Node)、prev(指向前一个 Node)。初始化时必须同时指定 prev 和 next,否则链接关系一上来就断了。
常见错误现象:
手写模拟 LinkedList 时只 new Node(e),没传 prev 和 next,结果所有节点都孤立,first 和 last 指向正确,但遍历时一走就 NullPointerException;
误以为 next 或 prev 可以后期赋值,其实链表逻辑全靠构造时就定好的引用关系。
-
Node是private static class,你不能直接 new 它,也不该绕过 LinkedList 的 add/remove 方法去操作它 - 空链表时
first == null && last == null,不是某个字段为new Node(null, null, null) - 首尾节点的
prev或next必为null—— LinkedList 是双向链表,不是双向循环链表(网上有误传)
linkFirst / linkLast 怎么改指针:为什么插入头尾都是 O(1)
在头部加元素,本质就是让新节点成为新的 first,并把原 first 的 prev 指向它;尾部同理,让新节点接在 last 后面,再更新 last。关键在于:这些操作都不需要遍历,只动几个引用。
容易踩的坑:
自己实现时忘记判空,比如在空链表上调用 linkLast 却没把 first 也设成新节点,导致 first == null && last != null,后续 get(0) 直接 NPE;
调换赋值顺序,比如先改 last = newNode 再改 l.next = newNode,此时 l 已经不是原来的尾节点,链接就错了。
- 正确顺序永远是:先保存旧节点(
final Node<E> l = last),再建新节点(带好prev/next),最后更新last和旧节点的next -
linkBefore(E e, Node<E> succ)的前提是succ != null,传 null 会直接抛NullPointerException,不是优雅返回
unlink(Node x) 删除节点时指针怎么“剪断”:为什么删中间也能 O(1)
删一个已知节点 x,核心动作就四步:拿到它的 prev 和 next,让 prev.next = next,让 next.prev = prev,再清掉 x 自身的引用(避免内存泄漏)。整个过程不找位置、不遍历,所以只要给你节点本身,删就是常数时间。
但注意:
这个高效只成立在「你已经持有那个 Node 对象」的前提下。而 remove(Object o) 要先遍历找匹配的 item,那还是 O(n);unlink 是 package-private 方法,你代码里调不到,只能通过 removeFirst()、removeLast() 或迭代器的 remove() 间接触发。
- 删首节点时,
prev == null,所以跳过prev.next = next,直接first = next - 删尾节点时,
next == null,跳过next.prev = prev,直接last = prev - 删完必须置空
x.item、x.prev、x.next,否则 GC 无法回收该节点(JDK 源码确实这么干)
什么时候真该关心 Node:别在不该碰的地方硬改指针
绝大多数业务代码完全不需要知道 Node 的存在。你用 add(int index, E e)、get(int index)、iterator(),LinkedList 内部会自动算从头还是从尾遍历更快(基于 index < size/2 判断),然后找到对应 Node —— 这个过程对你透明。
只有两类情况你会被迫和 Node 打交道:
写单元测试 mock 行为(极少);
调试时 dump 链表状态,比如发现 size == 5 但遍历只出来 3 个,就得顺着 first→next→… 手动查哪断了。
- 永远不要试图通过反射获取或修改
first/last,它们是transient字段,序列化时被忽略,且 JDK 后续版本可能改结构 - 用
toArray()或增强 for 循环比手动 while(node != null) 更安全,后者容易漏判node.next == null导致死循环(尤其并发修改时) - 如果真要自定义链表,别 copy
Node结构就完事 —— 缺少modCount和 fail-fast 机制,迭代中修改会静默出错

















