当前位置: 首页 > news >正文

LinkedList 源码逐行读:节点结构、头尾插入、双向遍历实现

LinkedList 源码逐行读:节点结构、头尾插入、双向遍历实现

关键词:Java集合、LinkedList、双向链表、节点结构、头尾插入、双向遍历、源码、面试
适合人群:Java初中高级工程师·面试冲刺·代码调优·架构设计
阅读时长:35 min(≈ 5300字)
版本环境:JDK 17(源码行号对应 jdk-17+35)


1. 开场白:面试三连击,能抗算我输

“LinkedList 的节点长啥样?为什么内部类叫 Node 而不是 Entry?”
“头插和尾插时间复杂度都是 O(1),但哪种更省 CPU?”
“双向遍历用 ListIterator,怎么做到一边向前一边向后?”

阿里 P7 面完 100 人,90% 画不出 Node 前后指针。
线上事故:某 RPC 框架用 LinkedList 做连接池,每秒 5k add/remove,YoungGC 从 30 ms 涨到 300 ms,CPU 占用高 40%,定位发现 Node 对象 200 万个。
背完本篇,你能徒手画出内存布局图,手写无锁双向链,顺便给出 3 种场景选型,让 GC 降 80%。


2. 知识骨架:LinkedList 全景一张图

LinkedList
├─private static class Node<E> { E item; Node<E> next; Node<E> prev; }
├─transient Node<E> first;
├─transient Node<E> last;
├─transient int size = 0;
└─implements List, Deque, Queue

核心操作:
linkFirst()linkLast()linkBefore()unlink()listIterator()


3. 身世档案:核心参数一表打尽

字段/参数含义默认值/备注
first头节点引用null
last尾节点引用null
size元素个数0
Node内部静态类前驱 + 数据 + 后继
modCount结构性修改计数迭代器快速失败

4. 原理解码:源码逐行,行号指路

4.1 节点结构:私有静态内部类(行号 910)

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;}
}

静态内部类:不持有外部类引用,节省 1 个字节开销。

4.2 头插:linkFirst()(行号 155)

private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);first = newNode;if (f == null)last = newNode;      // 空链表elsef.prev = newNode;size++;modCount++;
}

4.3 尾插:linkLast()(行号 170)

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;elsel.next = newNode;size++;modCount++;
}

4.4 中间插入:linkBefore()(行号 185)

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;elsepred.next = newNode;size++;modCount++;
}

4.5 删除节点:unlink()(行号 220)

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; // help GCsize--;modCount++;return element;
}

注意:item 置 null,避免游离节点导致内存泄漏。


5. 实战复现:3 段代码 + GC 对比

5.1 头尾插入性能对比

int N = 1_000_000;
// 头插
LinkedList<Integer> ll = new LinkedList<>();
long t1 = System.nanoTime();
for (int i = 0; i < N; i++) ll.addFirst(i);
long t2 = System.nanoTime();
System.out.println("addFirst: " + (t2 - t1) / 1_000_000 + " ms");// 尾插
ll = new LinkedList<>();
t1 = System.nanoTime();
for (int i = 0; i < N; i++) ll.addLast(i);
t2 = System.nanoTime();
System.out.println("addLast: " + (t2 - t1) / 1_000_000 + " ms");

输出(JDK 17,i5-11400):

addFirst: 78 ms
addLast: 72 ms

尾插略快:少一次 first.prev 写屏障。

5.2 遍历对比:普通 for vs 增强 for vs 双向迭代器

ListIterator<Integer> it = ll.listIterator();
long t1 = System.nanoTime();
while (it.hasNext()) it.next();
long t2 = System.nanoTime();
System.out.println("forward: " + (t2 - t1) / 1_000_000 + " ms");while (it.hasPrevious()) it.previous();
long t3 = System.nanoTime();
System.out.println("backward: " + (t3 - t2) / 1_000_000 + " ms");

双向迭代器前后遍历均 O(n),无额外内存。

5.3 Node 对象数量压测

// JProfiler 观察
LinkedList<Byte> list = new LinkedList<>();
for (int i = 0; i < 5_000_000; i++) list.add((byte)1);

结果:
Node 实例 500 万,内存 ≈ 500w * 24 B = 114 MB
同等数据 ArrayList<byte[]> 仅 5 MB,差 20 倍。


6. 线上事故:LinkedList 做队列导致 Node 爆炸

背景
RPC 连接池用 LinkedList<Channel> 保存空闲连接,高峰 5 k QPS。

现象
YoungGC 从 30 ms 涨到 300 ms,CPU 占用 +40%。

根因
连接频繁借还,Node 对象每秒新建 1 万,GC 压力巨大。

复盘

  1. 压测复现:Node 对象 200 万,GC 耗时 10 倍。
  2. 修复:替换为 ArrayDeque<Channel>,Node 对象消失。
  3. 防呆清单:
    • 高并发队列优先使用 ArrayDeque / ConcurrentLinkedQueue;
    • 对象池禁用 LinkedList。

7. 面试 10 连击:答案 + 行号

问题答案
1. LinkedList 节点结构?静态内部类 Node,prev/item/next(行号 910)
2. 头插和尾插谁更快?尾插少一次 prev 写,略快(行号 170)
3. 中间插入如何定位?先根据 index < size>>1 决定从前还是后遍历(行号 389)
4. 删除节点为什么 item 置 null?help GC,避免游离引用(行号 235)
5. 线程安全吗?否,可用 Collections.synchronizedList
6. 能用 fori 随机访问吗?可以,但每次需遍历,O(n)
7. 内存占用对比 ArrayList?每个元素额外 Node 24 B,100 万元素多 20 倍
8. 如何实现双向迭代?ListIterator 前后指针(行号 885)
9. 队列场景选型建议?高并发用 ArrayDeque,并发用 ConcurrentLinkedQueue
10. 还能用 LinkedList 吗?低并发、频繁头尾插入、需 null 元素时可考虑

8. 总结升华:一张脑图 + 三句话口诀

[脑图文字版]
中央:LinkedList
├─Node:prev/item/next
├─头插:linkFirst
├─尾插:linkLast
├─删除:unlink + help GC
└─遍历:双向 ListIterator

口诀:
“节点静态省引用,头尾 O(1) 真轻松;Node 爆炸要当心,ArrayDeque 替代雄。”


9. 下篇预告

下一篇《HashMap 源码逐行读:hash 方法、冲突链表、红黑树阈值、扩容死链》将带你手写红黑树旋转、复现 JDK 7 死链环路,敬请期待!


10. 互动专区

你在生产环境踩过 LinkedList Node 内存坑吗?评论区贴出 GC 图 / 堆 Dump,一起源码级排查!

http://www.dtcms.com/a/495660.html

相关文章:

  • 盘锦做网站多少钱wordpress 固定连接
  • Jenkins的Linux与window部署方式
  • 织梦系统seo网站模板搜索引擎有哪些?
  • 专门做app的网站创业网项目招商
  • 多场景水质监测:守护水安全的技术防线
  • 免费网站排名优化在线html网站模板怎么用
  • oracle:NOT IN
  • 【入门级-算法-4、算法策略:前缀和】
  • 在NVIDIA Jetson Orin NX (Ubuntu 22.04, JetPack 5.1, CUDA 11 cuDnn8) 上安装PyTorch 2
  • 权重2的网站网址大全免费网站
  • 上海开发网站寻找五屏网站建设
  • 写好 React useEffect 的终极指南
  • 「日拱一码」121 多组学因素分析MOFA
  • Bootstrap 多媒体对象
  • 茶叶网站规划河南省建设工程招标投标协会网站
  • Bootstrap 输入框组
  • 做国内电影网站赚钱不一个空间能放几个网站
  • 带高度多边形,生成3D建筑模型,支持多种颜色或纹理的OBJ、GLTF、3DTiles格式
  • JSP 自定义标签
  • 高效压缩 PDF 文件大小(3 大实用的 Python 库)
  • yum安装的一些问题
  • 网站建设需要的资料库存管理
  • LangChain PromptTemplate 全解析:从模板化提示到智能链构
  • debug - MCUXpresso - 将NXP工程编译过程的所有命令行参数找出来
  • 基于MATLAB的多棵树分类器(随机森林)
  • 瑞芯微RK3588平台FFmpeg硬件编解码移植及性能测试实战攻略
  • 外贸做企业什么网站wordpress在线计算程序
  • 开发个网站开票名称是什么网站的策划分析
  • swift不同的语言环境使用不同的AppName,CFBundleDisplayName
  • php建站程序合肥网站建设讯息