HashMap的put操作先处理null键(存索引0),再对非null键扰动哈希(h^(h>>>16)),通过(n-1)&hash定位桶;桶空则插入,非空则用==、hash、equals三重比对key,匹配则更新,否则尾插链表或红黑树;链表≥8且数组≥64时树化,size超阈值(容量×0.75)时扩容。

Java 中 HashMap 的 put 操作不是简单“算个 hash 就塞进去”,而是一套严谨的定位、比对与插入流程。核心目标是:**快速找到桶(bucket),准确识别是否为同一 key,再决定更新还是新增**。整个过程兼顾效率与正确性,尤其在处理 null、哈希冲突和扩容时有明确规则。
键的哈希计算与扰动处理
put 开始时先处理 key 的哈希值:
- 若 key 为 null,直接定为哈希值 0,后续固定落入数组索引 0 的位置;
- 若 key 非 null,调用
key.hashCode()获取原始哈希码,再经扰动函数处理:h ^ (h >>> 16)—— 这一步把高 16 位和低 16 位异或,让哈希值更均匀,显著降低低位相同导致的聚集冲突; - 该扰动后的值才是后续定位桶所用的最终 hash 值。
桶位置的快速定位(位运算)
HashMap 底层数组长度始终是 2 的幂(如 16、32、64…),因此用位与替代取模来算下标:
- 公式为:
index = (table.length - 1) & hash; - 例如数组长度为 16(二进制
10000),length-1 = 15(01111),与 hash 做 & 运算,等价于只保留 hash 的低 4 位,结果必在 [0,15] 范围内; - 这种位运算是无分支、极快的,避免了代价更高的 % 运算。
桶内查找与键匹配逻辑
定位到数组索引后,并不直接插入,而是严格判断是否已存在相同 key:
立即学习“Java免费学习笔记(深入)”;
- 先检查该位置是否为空(
tab[index] == null):为空则新建 Node 直接放入; - 非空时,依次比对:
① 先判断引用是否相同(node.key == key);
② 再判断 hash 值是否相等(node.hash == hash);
③ 最后才调用key.equals(node.key); - 三者同时满足,视为同一 key,执行 value 覆盖,并返回旧值;
- 只要任一条件不满足,就进入冲突处理流程(链表尾插或红黑树插入)。
冲突处理与结构升级条件
当桶中已有元素且 key 不匹配时,HashMap 按以下方式组织新节点:
- 如果当前桶是普通 Node(链表),新节点追加到链表末尾;
- 如果链表长度 ≥ 8 且 数组总长度 ≥ 64,则触发树化:链表转为红黑树,后续插入走树的平衡逻辑;
- 如果当前桶已是 TreeNode(红黑树),则直接在树中按 key 的自然顺序或比较器插入;
- 注意:树化是“升维”操作,但退化(树转链表)发生在 resize 后树节点 ≤ 6 时,不是实时检测。
插入后检查扩容阈值
每次成功插入一个新键值对(非覆盖),size 加 1,并检查是否需扩容:
- 扩容触发条件:
size > threshold(threshold = capacity × loadFactor,默认 0.75); - 扩容时创建新数组,容量翻倍(如从 16→32),所有已有节点重新计算
(newLength-1) & hash并散列到新桶中; - 扩容是重操作,应根据预估数据量合理设置初始容量,减少 rehash 次数。


















