hashmap存、找、变三步:先用key.hashcode()扰动后位运算定位node桶;桶内以链表或红黑树(≥8且数组≥64)存储节点;扩容时新建2倍数组并重哈希分发,但非线程安全。

面试时讲 HashMap 原理,关键不是堆砌术语,而是用清晰逻辑把“它怎么存、怎么找、怎么变”三件事说透。考官真正想听的,是你是否理解设计背后的权衡,而不是复述博客里的“数组+链表+红黑树”七个字。
桶里到底存什么?别答“键值对”
桶(bucket)里存的是一个节点引用——JDK 8 是 Node
这个细节一错,后续扩容、树化、并发问题全会卡壳。
定位桶的完整链路要讲准
从 key 到最终位置,一共三步:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 调用 key.hashCode() 得到原始哈希值(int)
- 执行扰动运算:h ^ (h >>> 16),让高16位参与低位运算,避免低位重复导致大量冲突
- 用 (n - 1) & hash 计算下标,其中 n 是数组长度(必须是 2 的幂),这个位运算是取模的高效替代
如果只说“用哈希值对数组长度取模”,没提扰动和位运算,说明没看过源码或没理解设计意图。
链表转红黑树不是“到了8就转”
触发树化的两个条件必须同时满足:
- 链表长度 ≥ 8
- 当前数组长度 ≥ 64
为什么加第二条?因为数组太小(比如才16),元素扎堆是正常现象,此时优先扩容(rehash + 搬数据),比树化更轻量、更治本。只有当数组已经够大,还有桶持续堆积,才说明哈希分布确实差,值得升级为红黑树。同样,退化回链表的阈值是树节点数 ≤ 6,不是7或5。
扩容机制得带出并发风险点
扩容本质是新建两倍长的数组,再把老数组里每个桶的节点重新 hash、分发过去。JDK 8 改用尾插法插入新节点,解决了 JDK 7 头插法在多线程下可能引发的死循环问题。但 HashMap 本身仍非线程安全:put 过程中若发生扩容,多个线程同时操作同一链表,仍可能导致数据丢失或覆盖。这点常被追问,用来判断你是否真懂“为什么不能在并发场景直接用 HashMap”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










