# JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?

6 min read
Table of Contents

JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?

JDK 1.8 HashMap 核心改动详解

【核心定义】

JDK 1.8 对 HashMap 进行了多项底层优化,核心目标是在保持高效查找的同时,提升极端情况下的性能稳定性和内存使用效率。

【关键要点】

  1. 数据结构优化:引入红黑树替代链表

    • 当链表长度 ≥ 8 且桶数组长度 ≥ 64 时,链表转为红黑树(查找复杂度从 O(n) 降至 O(log n))
    • 当红黑树节点数 ≤ 6 时,退化为链表(避免过度开销)
  2. 扩容机制重构:优化rehash过程

    • 采用高位与运算替代取模运算((n-1) & hash)
    • 扩容时节点重分布规则:新位置 = 原位置 或 原位置 + 旧容量(利用hash高位bit判断)
  3. 插入算法改进:尾插法替代头插法

    • JDK 1.7 使用头插法(多线程下可能产生环形链表)
    • JDK 1.8 改为尾插法(解决并发死循环问题)
  4. 初始化时机调整:延迟初始化

    • 构造时不立即初始化底层数组,首次put时按需创建(节省内存)
  5. 哈希计算优化:高位参与运算

    • 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.7JDK 1.8
数据结构数组+链表数组+链表/红黑树
插入方式头插法尾插法
哈希计算4次扰动1次扰动+高位参与
扩容死锁可能发生基本避免
初始化构造时初始化首次put时初始化

『面试官追问』

  1. 为什么选择红黑树而不是AVL树?

    • 红黑树插入/删除旋转次数更少(平均性能更好)
    • 红黑树对平衡性要求更低(维护成本低)
  2. HashMap的负载因子为什么是0.75?

    • 空间与时间的折中:0.75时链表长度期望为0.5
    • 数学推导:log(2) ≈ 0.693,取0.75是工程经验值
  3. 多线程下HashMap仍然安全吗?

    • ❌ 不安全!尾插法只解决死循环,但仍有数据覆盖问题
    • 并发场景必须使用ConcurrentHashMap或Collections.synchronizedMap
  4. 哈希冲突的解决方案有哪些?

    • 开放定址法(线性探测、二次探测)
    • 链地址法(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兼容的同时,显著提升了极端场景下的性能表现和并发安全性。

My avatar

文章看完了,不打算去别的篇章转转吗?👀 感谢你的耐心阅读!如果你有问题想探讨,或者发现了文中的 Bug,请务必通过页脚的链接联系我


More Posts

# Kafka 中 Zookeeper 的作用?

7 min read

Zookeeper 在 Kafka 架构中扮演着分布式协调服务与元数据管理中心的角色,是 Kafka 实现高可用、分区容错和集群状态一致性的核心依赖组件。

Read

Comments