# JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
6 min read
Table of Contents
JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
JDK 1.8 HashMap 核心改动详解
【核心定义】
JDK 1.8 对 HashMap 进行了多项底层优化,核心目标是在保持高效查找的同时,提升极端情况下的性能稳定性和内存使用效率。
【关键要点】
-
数据结构优化:引入红黑树替代链表
- 当链表长度 ≥ 8 且桶数组长度 ≥ 64 时,链表转为红黑树(查找复杂度从 O(n) 降至 O(log n))
- 当红黑树节点数 ≤ 6 时,退化为链表(避免过度开销)
-
扩容机制重构:优化rehash过程
- 采用高位与运算替代取模运算(
(n-1) & hash) - 扩容时节点重分布规则:新位置 = 原位置 或 原位置 + 旧容量(利用hash高位bit判断)
- 采用高位与运算替代取模运算(
-
插入算法改进:尾插法替代头插法
- JDK 1.7 使用头插法(多线程下可能产生环形链表)
- JDK 1.8 改为尾插法(解决并发死循环问题)
-
初始化时机调整:延迟初始化
- 构造时不立即初始化底层数组,首次put时按需创建(节省内存)
-
哈希计算优化:高位参与运算
hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)- 让高位参与扰动,减少哈希碰撞
【深度推导/细节】
扩容重分布逻辑拆解(Step by Step)
Step 1:计算新位置
// oldCap = 16 (二进制 10000)// newCap = 32 (二进制 100000)// 关键:判断hash值在oldCap对应bit位是0还是1
if ((e.hash & oldCap) == 0) { // 结果为0 → 新索引 = 原索引 newTab[j] = loHead;} else { // 结果为1 → 新索引 = 原索引 + oldCap newTab[j + oldCap] = hiHead;}Step 2:性能优势
- 避免重新计算hash值
- 节点均匀分配到两个新桶(原桶和偏移桶)
- 时间复杂度从 O(n²) 优化到 O(n)
红黑树转换阈值设计原理
- 链表转树阈值 8:基于泊松分布,链表长度达到8的概率极低(约0.00000006)
- 树转链表阈值 6:设置缓冲区间(避免频繁转换的抖动开销)
- 最小树化容量 64:避免早期小表过度树化(空间开销大)
【关联/对比】
HashMap vs ConcurrentHashMap (JDK 1.8)
| 特性 | HashMap (1.8) | ConcurrentHashMap (1.8) |
|---|---|---|
| 线程安全 | ❌ 非线程安全 | ✅ 分段锁+CAS |
| 数据结构 | 数组+链表/红黑树 | 数组+链表/红黑树 |
| 空键值 | ✅ 允许1个null键 | ❌ 不允许null键值 |
| 锁粒度 | 无锁(非线程安全) | 桶级别锁(Node首节点) |
JDK 1.7 vs 1.8 关键差异
| 方面 | JDK 1.7 | JDK 1.8 |
|---|---|---|
| 数据结构 | 数组+链表 | 数组+链表/红黑树 |
| 插入方式 | 头插法 | 尾插法 |
| 哈希计算 | 4次扰动 | 1次扰动+高位参与 |
| 扩容死锁 | 可能发生 | 基本避免 |
| 初始化 | 构造时初始化 | 首次put时初始化 |
『面试官追问』
-
为什么选择红黑树而不是AVL树?
- 红黑树插入/删除旋转次数更少(平均性能更好)
- 红黑树对平衡性要求更低(维护成本低)
-
HashMap的负载因子为什么是0.75?
- 空间与时间的折中:0.75时链表长度期望为0.5
- 数学推导:
log(2) ≈ 0.693,取0.75是工程经验值
-
多线程下HashMap仍然安全吗?
- ❌ 不安全!尾插法只解决死循环,但仍有数据覆盖问题
- 并发场景必须使用ConcurrentHashMap或Collections.synchronizedMap
-
哈希冲突的解决方案有哪些?
- 开放定址法(线性探测、二次探测)
- 链地址法(HashMap采用)
- 再哈希法(双重哈希)
- 公共溢出区法
【版本差异补充】
- JDK 1.6:引入哈希种子(增加哈希随机性,防止碰撞攻击)
- JDK 1.7:重构哈希算法,但仍有头插法问题
- JDK 1.8:全面优化(树化、尾插、扩容优化)
- JDK 1.9+:进一步优化内部实现,但对外API保持兼容
【性能数据支撑】
- 理想情况(无冲突):O(1)
- 链表查询(冲突较少):O(n)
- 红黑树查询(冲突严重):O(log n)
- 扩容成本:平均O(1),最坏O(n)(但1.8优化后接近平均)
总结:JDK 1.8 的 HashMap 通过数据结构混合化、算法精细化、内存延迟化三大策略,在保持API兼容的同时,显著提升了极端场景下的性能表现和并发安全性。