hashmap的put()先处理null键(存入索引0),非null键则计算扰动哈希值,通过hash&(table.length-1)定位桶;桶空则新建node插入,桶非空则用==或equals比较key,存在则覆盖value,否则链表尾插或红黑树插入,并在size超阈值时扩容。

Java 中 HashMap 存储和获取键值对,核心靠三步:算哈希、定位置、查链/树。
存储过程:put() 是怎么放进去的?
调用 put(K key, V value) 时,实际执行以下逻辑:
- 先判断 key 是否为 null,null 键被固定放在数组索引 0 的位置(特殊处理);
- 非 null 键则调用 key.hashCode(),再经过扰动函数 (h ^ h>>>16) 降低低位碰撞概率;
- 用 hash & (table.length - 1) 快速计算数组下标(要求 table 长度必须是 2 的幂);
- 定位到桶(bucket)后:
- 桶为空 → 直接新建 Node 放入;
- 桶有元素 → 先用 == 或 equals() 比较 key 是否已存在,存在则覆盖 value;
- 不存在则根据当前结构选择插入方式:链表尾插(JDK 1.8+),或红黑树的插入逻辑;
- 插入后检查是否需扩容:当 size > threshold(容量 × 加载因子,默认 0.75)时触发 resize(),数组长度翻倍并重哈希所有元素。
获取过程:get() 是怎么找出来的?
调用 get(Object key) 时,并不遍历整个 map,而是精准跳转:
- 同样先处理 null 键:直接查 table[0];
- 非 null 键再次计算扰动后的 hash 值,再用 hash & (table.length - 1) 定位桶;
- 拿到桶首节点后:
- 如果是普通 Node,从头开始逐个比较 key(== 或 equals);
- 如果是 TreeNode(红黑树),走左/右子树比较逻辑,时间复杂度 O(log n);
- 匹配成功即返回 value,失败返回 null(注意:value 本身也可能为 null,不能据此判断 key 是否存在)。
为什么能又快又准?关键设计点
高效不是偶然,而是几处硬核设计共同作用:
-
数组索引位运算:用
hash & (length-1)替代取模%,大幅提升定位速度; - 链表 + 红黑树混合结构:链表解决小规模冲突,红黑树兜底长链(≥8 且数组 ≥64 才树化),避免最坏 O(n);
- 扰动哈希:把 hashCode 高16位异或进低16位,让高位也参与索引计算,缓解低位哈希分布不均问题;
- 桶内 key 比较严格:先比 hash 值(快速筛),再用 == 或 equals() 确认,兼顾性能与语义正确性。
注意事项:别踩这些坑
实际用的时候,有些细节直接影响行为和性能:
- 自定义类作 key 时,必须重写 hashCode() 和 equals(),否则 put 和 get 对不上;
- key 对象在放入后如果修改了影响 hashCode() 或 equals() 的字段,该 key 就再也取不到了(变成“幽灵键”);
- HashMap 不保证顺序,也不支持并发写入——多线程 put 可能导致死循环(JDK 1.7)或数据丢失(JDK 1.8+);
- 初始容量设太小会频繁扩容,设太大浪费内存;加载因子调太高(如 0.9)会增加冲突概率,拉低性能。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











