news 2026/6/10 10:48:35

深度剖析 HashMap:从 JDK 1.7 死循环到 1.8 高低位映射优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度剖析 HashMap:从 JDK 1.7 死循环到 1.8 高低位映射优化

作者:[予枫]

发布时间:2026年1月

分类:Java 后端 / 底层原理


一、 引言:哈希冲突与 HashMap 的使命

在计算机科学中,哈希表通过哈希函数将 Key 映射到数组下标,实现 $O(1)$ 的查找效率。然而,由于哈希函数输出空间有限,哈希冲突(Hash Collision)避不可免。

常见的解决冲突方法包括:

  • 链地址法(Separate Chaining):HashMap 采用的核心策略。

  • 开放定址法(Open Addressing):如线性探测、平方探测。

  • 再哈希法(Rehashing):使用多个哈希函数。

  • 建立公共溢出区

HashMap 在单线程环境下表现卓越,但在多线程这片“深水区”,它就像一个没有交通灯的十字路口,极易引发致命灾难。


二、 HashMap 的五大线程安全陷阱

多线程同时修改同一个 HashMap 实例时,会引发以下问题:

  1. 扩容死循环(JDK 1.7):最著名的 Bug,会导致 CPU 飙升至 100%。

  2. 数据丢失/覆盖(1.7 & 1.8 共有):多个线程同时put时,由于没有加锁,计算出的索引若相同,后者的值会覆盖前者。

  3. Size 计算不准size++操作非原子性,并发下会导致计数不一致。

  4. 扩容覆盖(JDK 1.8):多线程同时扩容生成多个新数组,最终只有最后一个线程的数组会被保留,其余线程插入的数据随之丢失。

  5. 快速失败(Fail-Fast):在迭代过程中若有其他线程修改结构,会立即抛出ConcurrentModificationException


三、 深度复现:JDK 1.7 的“死亡之环”

1. 源码现场

JDK 1.7 扩容的核心在于transfer方法,其罪魁祸首是头插法(Head Insertion)

void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; // 【关键点 1】记录下一个节点 int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; // 【关键点 2】头插到新表 newTable[i] = e; e = next; // 【关键点 3】移动到下一个 } while (e != null); } } }

2. 场景分析

假设原链表为A -> B -> null

  • 线程 T1执行到记录next = B后被挂起。此时 T1 视角:e = A, next = B

  • 线程 T2完成整个扩容,由于头插法,新链表变为B -> A -> null。此时 A 的next已指向null,而 B 的next指向了 A。

  • 线程 T1 恢复

    1. 处理 A:将 A 插入新表,e变为 B。

    2. 处理 B:此时由于 T2 修改了指针,B.next变成了 A。T1 记录next = A,将 B 插入 A 之前。

    3. 再次处理 A:此时e = AA.next被设为当前头节点 B。

    • 结局B.next = AA.next = B环形链表诞生

3. 后果

当后续调用get()落入此桶时,while遍历将永不停止,导致 CPU 占用率瞬间达到 100%。


四、 JDK 1.8 的救赎:高低位映射全解析

JDK 1.8 废弃了头插法,改用尾插法并引入了高低位映射(High-Low Mapping)

1. 数学基石:2 的幂次方

HashMap 容量 $n$ 始终为 $2^k$。扩容时,新容量是旧容量的 2 倍。

  • 索引计算:$Index = (n-1) \& hash$。

  • 奇妙现象:扩容后,掩码(Mask)仅在高位多出一个1。这意味着元素新索引只有两种可能:原位置原位置 + 旧容量

2. 核心算法:四指针分流

1.8 使用loHead, loTail(低位链表)和hiHead, hiTail(高位链表)进行分选:

  • 判断条件(e.hash & oldCap) == 0

  • 低位(0):留在原位置。

  • 高位(1):搬移到index + oldCap位置。

3. 进阶:红黑树的split细节

若桶内是红黑树,扩容时调用TreeNode.split()

  • 同样按高低位拆分为两个双向链表。

  • 去树化:若拆分后长度 $\le 6$,转回普通链表。

  • 树化:若长度 $> 6$,重新构建红黑树。


五、 总结与最佳实践

为什么 1.8 的设计更优?

特性1.8 高低位映射核心价值
位运算优化(hash & oldCap)极速判定,无需 rehash,性能翻倍。
尾插法保持原序彻底解决死循环问题。
树结构拆分split逻辑保证扩容后大桶依然具备 $O(\log n)$ 的检索效率。

解决方案

在多线程环境下,请务必遵守:

  1. ConcurrentHashMap(首选):采用 CAS +synchronized细粒度锁,支持多线程协同扩容,是生产环境的最佳选择。

  2. Collections.synchronizedMap:全表加锁,性能较低。


结语:

理解 HashMap 的演进不仅仅是为了面试,更是为了学习其背后精妙的位运算设计与并发处理思路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 5:00:46

Agent全面爆发!一文搞懂Agent开发核心链路

过去一年&#xff0c;「智能体&#xff08;Agent&#xff09;」这个词的含义悄悄变了。 最早大家聊的是&#xff1a; 模型够不够聪明&#xff1f; 回答像不像人&#xff1f; 而现在&#xff0c;越来越多团队在问的是&#xff1a; 它能不能自己判断&#xff1f; 能不能自己…

作者头像 李华
网站建设 2026/6/8 7:55:39

投资理财智能助手的基本概念

投资理财智能助手的基本概念 关键词:投资理财智能助手、人工智能、金融科技、个性化服务、数据驱动 摘要:本文深入探讨了投资理财智能助手的基本概念,旨在为读者全面介绍这一新兴领域。首先阐述了研究的目的和范围,明确预期读者,概述文档结构并解释相关术语。接着详细介绍…

作者头像 李华
网站建设 2026/6/9 7:24:57

【课程设计/毕业设计】python基于CNN机器学习的遥感图片识别沙漠湖泊和森林基于CNN深度学习的遥感图片识别沙漠湖泊和森林

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/9 23:14:32

华为OD机考双机位B卷 - 组装新的数组 (Java Python JS C/C++ GO )

最新华为上机考试 真题目录&#xff1a;点击查看目录 华为OD面试真题精选&#xff1a;点击立即查看 华为OD机考双机位B卷 - 组装新的数组 题目描述 给你一个整数M和数组N&#xff0c;N中的元素为连续整数&#xff0c;要求根据N中的元素组装成新的数组R&#xff0c;组装规则…

作者头像 李华
网站建设 2026/5/31 16:37:18

AI产品经理实战录:如何“啃”下AI这块硬骨头

在我分享过的《B端产品从0到1全流程》等文章中&#xff0c;我反复强调“从业务中来&#xff0c;到业务中去”的核心心法。当这股无法回避的AI浪潮席卷而来时&#xff0c;我看到的不是一片需要膜拜的技术迷雾&#xff0c;而是一个个亟待用产品思维去定义、翻译和重构的真实业务问…

作者头像 李华