HashMap put核心是“计算槽位→处理冲突→更新结构”:先扰动哈希值(h ^ (h>>>16))提升低位均匀性,再用(n-1)&hash定位桶,接着按链表或红黑树逻辑插入/覆盖,最后阈值触发扩容并拆分迁移。

HashMap 的 put 方法核心是“计算槽位→处理冲突→更新结构”,槽位计算依赖哈希值与数组长度的位运算,而非简单取模。
哈希值扰动:让高位也参与索引计算
直接用 key.hashCode() 可能导致低比特位分布不均,尤其当数组长度为 2 的幂次时,仅低几位决定位置,容易引发大量哈希碰撞。HashMap 对原始哈希值做了扰动:
- 执行
(h = key.hashCode()) ^ (h >>> 16),把高16位异或到低16位 - 这样即使 hashCode 集中在低位,扰动后也能更均匀地影响整个哈希值
- 例如:hashCode=0x0000abcd(十进制43981),右移16位得0x00000000,异或后仍是0x0000abcd;而 hashCode=0xabcd0000,扰动后变成 0xabcdabce,低位明显变化
槽位定位:用位与替代取模,要求容量必须是2的幂
数组下标通过 (n - 1) & hash 计算,其中 n 是 table.length。这等价于 hash % n,但前提是 n 为 2 的整数次幂:
- 若 n = 16(0b10000),则 n−1 = 15(0b01111),
hash & 15实际取 hash 的低 4 位 - 这种位运算是 CPU 友好的,比取模快得多
- 所以 HashMap 在扩容时总是将容量设为 2 的幂(如 16→32→64),并通过 tableSizeFor 确保初始容量也被提升到最近的 2 次幂
桶中插入逻辑:区分链表与红黑树,处理重复 key
定位到桶(table[i])后,根据首节点类型分路径处理:
- 如果桶为空(
tab[i] == null),直接新建 Node 存入 - 如果首节点 hash 和 key 匹配(
e.hash == hash && ((k = e.key) == key || key.equals(k))),直接覆盖 value - 如果首节点是 TreeNode(红黑树),调用
putTreeVal按红黑树规则插入或更新 - 如果是普通 Node(链表),遍历查找:遇到相同 key 则覆盖;遍历结束未找到,则尾插新节点;若链表长度 ≥ 8 且 table.length ≥ 64,触发树化
扩容判断与迁移:阈值触发,拆分链表/树
put 后检查是否需扩容:size >= threshold(默认 0.75 × capacity):
- 扩容生成新数组,容量翻倍(如 16→32)
- 原桶中每个元素重哈希,因新容量仍是 2 的幂,只需看新增的最高位是否为 1,就能确定它属于原位置(lo)还是原位置+旧容量(hi)
- 链表用两个指针分别收集 lo/hi 分支,避免重复遍历;红黑树也按 same bin / split 两类迁移,必要时可能退化为链表
整个流程兼顾性能与一致性:扰动哈希提升散列质量,位与索引保证速度,延迟树化平衡查找开销,扩容拆分维持负载均衡。不复杂但容易忽略细节。

















