6.HashMap 从 JDK7 到 JDK21 的演进

HashMap 从 JDK7 到 JDK21 的演进(含红黑树化机制)

🚀 高频指数:★★★★★
🎯 你将收获:底层结构演变、扩容与树化机制、hash冲突解决、并发风险与源码逻辑图。


一、为什么 HashMap 面试永远问不完?

因为它融合了:

  • 数组、链表、红黑树三种数据结构;
  • 哈希算法、扩容逻辑、并发原理;
  • JVM 内存布局与 GC 行为。

☕️ 一句话总结:“一个 HashMap = 一部 Java 底层史。”


二、结构总览:数组 + 链表 + 红黑树

组成部分说明
Node<K,V>[] table主数组(哈希桶)
Node<K,V>.next冲突时形成链表
TreeNode<K,V>冲突过多时转为红黑树
threshold扩容阈值(容量 × 负载因子)
loadFactor默认0.75,平衡空间与查找效率

三、hash 冲突与扰动函数

1️⃣ key → hash

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

🔹 高16位与低16位异或,减少碰撞。
🔹 确保高位参与寻址,分布更均匀。

2️⃣ 取模定位桶

int index = (n - 1) & hash;

✅ 位运算代替 %,提升性能。
例如容量16 → (16-1)=15 → 按位与实现快速取模。


四、JDK7 与 JDK8 的重大差异

特性JDK7JDK8
存储结构数组 + 链表数组 + 链表 + 红黑树
插入方式头插法(链表逆序)尾插法(链表顺序)
扩容实现重新计算每个元素位置利用 hash 位优化分配
初始化时机延迟到第一次 put同样延迟
遍历顺序不保证顺序同样不保证
并发问题存在死循环风险已解决(尾插+安全迁移)

☕️ 口诀:“七头八尾,八树化。”


五、核心源码解析(JDK8)

1️⃣ put 流程简图

hash → 定位桶 → 无元素则新建Node
               → 有元素则:
                   - key相同 → 覆盖value
                   - key不同 → 链表/树追加节点
                   - 链表长度≥8 → 树化
                   - size > threshold → 扩容

2️⃣ treeifyBin 核心逻辑

if (tab == null || (n = tab.length) < 64)
    resize(); // 数组太小先扩容
else
    tab[i] = new TreeNode<>(...); // 链表转红黑树

树化条件:

  • 链表长度 ≥ 8;
  • 且数组容量 ≥ 64;
    否则优先扩容而非树化。

六、扩容机制详解

扩容阈值计算

threshold = oldCapacity * loadFactor;

resize() 方法逻辑

newCap = oldCap << 1;  // ×2 扩容
for (Node<K,V> e : oldTab) {
    if (e.hash & oldCap == 0)
        newTab[j] = e;        // 留在原桶
    else
        newTab[j + oldCap] = e; // 移至新桶
}

⚙️ 利用 hash 位的“高1/低0”判断是否迁移 → 无需重新计算 hash。

📌 口诀:“低位留原,高位加偏。”


七、红黑树化机制(JDK8+)

链表节点超过8时树化(TreeNode 结构):

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent, left, right, prev;
    boolean red;
}
  • 插入、删除后会自动旋转、着色;
  • 当桶中元素数量 < 6 时退化为链表。

✅ 树化提升最差复杂度从 O(n) → O(log n)。


八、hashCode、equals、冲突与覆盖

put() 时判断 key 相等逻辑:

if (p.hash == hash && (p.key == key || key.equals(p.key))) {
    p.value = newValue; // 覆盖
}

所以必须保证:equals 与 hashCode 一致性


九、JDK21 中 HashMap 的变化

🆕 自 JDK19 起,HashMap 内部已针对高性能场景做进一步优化。

版本优化内容
JDK19增加随机种子 hash,防御哈希碰撞攻击
JDK20改进 resize 逻辑,减少复制开销
JDK21支持“Compact HashMap”实验性特性(部分场景内存优化)
JDK21+新增 computeIfAbsent 等 Lambda 优化内联

☕️ 趋势:从“稳定结构”向“高并发与内存友好”方向演进。


十、面试官追问清单

面试问题答题要点
HashMap 初始容量?默认16,负载因子0.75
扩容时机?size > capacity × loadFactor
链表树化条件?链表长度≥8且容量≥64
为什么用红黑树?保证查找O(log n),避免退化
JDK7 与 JDK8 最大区别?链表头插→尾插 + 树化机制
并发风险?JDK7 多线程扩容死循环,JDK8已改进但仍非线程安全

十一、项目实践建议

场景建议
数据量已知new HashMap<>(expectedSize * 4 / 3) 避免扩容
多线程环境使用 ConcurrentHashMap
Key 为自定义类必须重写 equals 与 hashCode
高性能缓存调整负载因子到0.6–0.7 平衡空间与效率

十二、口诀记忆

☕️ “数组为体,链表为骨,红黑为盾,扩容自愈。”

补充版:
“七头八尾八树化,六退回链六十四。”


十三、小结

知识点要点
数据结构数组 + 链表 + 红黑树
默认参数初始容量16,负载因子0.75
扩容策略×2扩容,重新分配桶
树化规则链表≥8且容量≥64
性能复杂度O(1) → O(log n)(树化后)
并发安全非线程安全(推荐ConcurrentHashMap)

✅ 一句话总结:“HashMap 一半是算法,一半是艺术。”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的代码

如果您有受益,欢迎打赏博主😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值