外观
一句话答案
容量翻倍后重新哈希,JDK8 优化:通过 (hash & oldCap) 判断元素在原位还是原位+oldCap,避免重新计算哈希。
核心要点
触发条件: size > threshold(threshold = capacity × loadFactor,默认 16 × 0.75 = 12)
扩容流程:
- 新容量 = 旧容量 × 2(始终保持 2 的幂次)
- 创建新的数组(
newTab = new Node[newCap]) - Rehash:遍历旧数组,将每个元素重新计算位置放入新数组
JDK 8 的优化(无需重新计算 hash):
- 旧容量 = 16(
...0001 0000),新容量 = 32(...0010 0000) - 新增的判断位就是旧容量对应的那一位(第5位)
- 原位置的元素只有两种去向:
- 原下标(hash 第5位 = 0)
- 原下标 + 旧容量(hash 第5位 = 1)
java
// 判断元素去向,只需一次位运算
if ((e.hash & oldCap) == 0) {
// 放入 lo 链(原下标)
} else {
// 放入 hi 链(原下标 + oldCap)
}这个设计避免了对每个 key 重新做 hash() 计算,显著提升扩容性能。
负载因子为什么是 0.75:
- 太大(如 0.9):冲突多,链表长,查询慢
- 太小(如 0.5):扩容频繁,空间浪费
- 0.75 是时间与空间的折中(泊松分布下 0.75 时碰撞概率最低)
追问与易错
追问方向:
- 扩容时元素怎么重新分配?JDK8 优化了什么?(不重新算 hash,看高位 bit)
- 扩容是线程安全的吗?(不安全,JDK7 并发扩容可能死循环)
- 负载因子为什么是 0.75?(时间和空间的折中)
易错点:
- ❌ 认为扩容时所有元素都要重新计算 hash——JDK8 通过 (hash & oldCap) 判断新位置
- ❌ 扩容只是数组变大——还需要将链表/红黑树中的节点重新分配
💡 记忆锚点
住满75%翻倍盖新楼,JDK8搬家不用重算房号:只看hash新增的那一位——0留原地,1搬到"原房号+旧楼层数"。一次位运算定去留,搬家效率翻倍。