
HashMap put操作时键重复了,到底覆盖还是新增?
Java 的 HashMap 在 put() 时遇到相同键(equals() 为 true 且 hashCode() 相等),**直接覆盖旧值,不新增节点**。这不是“冲突处理”,而是语义定义——键唯一是 Map 接口契约。
常见错误现象:反复 put("id", obj1) 再 put("id", obj2),结果只留 obj2,误以为是哈希碰撞导致丢失。
- 判断“相同键”的两个条件必须同时满足:
hashCode()相等 +equals()返回true - 若自定义类作键,**必须重写
hashCode()和equals(),且逻辑一致**;否则可能键看似相同却无法命中 - 使用
String、Integer等 JDK 类型作键时,这点已内置保障,无需额外操作
哈希碰撞发生时,HashMap内部怎么存?
哈希碰撞指不同键算出相同桶索引(hash & (capacity - 1)),此时 HashMap 不丢数据,而是用链表或红黑树组织同桶内多个节点。
实际行为取决于 Java 版本和桶内节点数:
立即学习“Java免费学习笔记(深入)”;
- JDK 8+:单桶节点 ≤ 7 用链表;≥ 8 且
table.length ≥ 64时转为红黑树 - 链表转树的阈值是硬编码的
TREEIFY_THRESHOLD = 8,但前提是数组长度够大,否则先扩容 - 红黑树退化回链表的阈值是
UNTREEIFY_THRESHOLD = 6,发生在 resize 或 remove 后节点减少时
示例:两个对象 a 和 b 的 hashCode() 都是 100,容量为 16,则都映射到桶索引 100 & 15 = 4,它们会挂在 table[4] 的链表上(或树中)。
为什么重写 equals 不重写 hashCode 就会找不到键?
因为 get() 查找分两步:先用 hashCode() 定位桶,再在桶内遍历比对 equals()。如果 hashCode() 不一致,压根不会进那个桶,equals() 根本没机会执行。
典型错误场景:
- 自定义类
User只重写了equals(),但hashCode()仍用Object默认实现(基于内存地址) -
map.put(new User("a"), 1)和map.get(new User("a"))返回null—— 两个User实例equals()为true,但hashCode()不同,查不到 - IDE 自动生成的
hashCode()和equals()是安全的;手写时务必确保:相等的对象必须有相同hashCode()
扩容时链表节点迁移,为啥要分高低位?
JDK 8 的 resize() 把原桶链表拆成两个新桶(高位/低位),是为了避免 rehash 全量计算,提升迁移效率。
核心原理:扩容后容量翻倍(如 16→32),新桶索引只比旧索引多一位 bit。这个 bit 是 0 还是 1,决定了节点去低区还是高区。
- 假设旧容量 16(
1111),新容量 32(11111);原索引由hash & 15得到,新索引由hash & 31得到 - 多出来的那一位就是
hash & 16的结果:为 0 → 低位桶(索引不变);为 1 → 高位桶(索引 = 原索引 + 16) - 这个拆分让迁移只需一次遍历、按位判断,不用重新
hash()每个键
容易被忽略的是:这个优化只在使用 2 的幂次容量时成立;如果你传入非 2 幂初始容量(如 new HashMap(10)),HashMap 会自动向上取整到 16,所以实际仍走这套逻辑。









