‌HashMap 1.8 的扩容机制(Resize)‌

这是 HashMap 性能优化的核心,也是面试中区分"背题"和"真懂"的分水岭。1.8 在扩容时做了一个非常巧妙的优化:‌不需要重新计算哈希值,而是通过位运算直接判断节点去新数组的哪个位置。‌

  1. 核心优化:为什么 1.8 扩容更快?
    在 1.7 中,扩容时需要遍历所有节点,对每个 key ‌重新计算 hash‌ (hash(key) % newCapacity),这非常耗时。

在 1.8 中,因为容量始终是 ‌2 的幂次方‌,利用了一个数学特性:

元素在扩容后的新位置,要么在「原下标」,要么在「原下标 + 旧容量」。‌

原理推导

假设旧容量 oldCap = 16 (二进制 10000),新容量 newCap = 32 (二进制 100000)。

某个 key 的 hash 值为 H。

旧下标‌:H & (16 - 1) 即 H & 15 (...01111)

新下标‌:H & (32 - 1) 即 H & 31 (...11111)

区别仅在于第 5 位(从右往左数,对应 16 的那一位)是 0 还是 1:‌

如果 H & oldCap == 0:说明第 5 位是 0,新下标 = 旧下标。

如果 H & oldCap != 0:说明第 5 位是 1,新下标 = 旧下标 + oldCap。

结论‌:只需要做一次 hash & oldCap 的位运算,就能决定节点去向,无需重新计算整个 hash。

  1. 扩容流程图解(高位/低位链表拆分)
    1.8 在扩容时,会将原链表拆分成两个新链表:

低位链表 (loHead/loTail)‌:去往「原下标」

高位链表 (hiHead/hiTail)‌:去往「原下标 + 旧容量」

示例

假设 oldCap = 16,桶位 index=2 处有链表:A -> B -> C

判断 A‌:hash(A) & 16 == 0 → 放入低位链表

判断 B‌:hash(B) & 16 != 0 → 放入高位链表

判断 C‌:hash© & 16 == 0 → 放入低位链表

结果:‌

低位链表‌ (index=2):A -> C

高位链表‌ (index=2+16=18):B

注意:这里使用的是‌尾插法逻辑‌(维护 loTail/hiTail),所以顺序保持为 A->C 和 B,‌不会反转‌,从而避免了 1.7 的死循环问题。

  1. 核心代码逻辑(简化版)
java 复制代码
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int newCap = oldCap << 1; // 容量翻倍
    
    // ...省略阈值判断...

    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;

    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null; // 帮助GC
                
                if (e.next == null) // 只有一个节点,直接算新位置
                    newTab[e.hash & (newCap - 1)] = e;
                
                else if (e instanceof TreeNode) // 红黑树处理
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                else { // 链表处理:核心优化逻辑
                    Node<K,V> loHead = null, loTail = null; // 低位链表
                    Node<K,V> hiHead = null, hiTail = null; // 高位链表
                    Node<K,V> next;
                    
                    do {
                        next = e.next;
                        // 关键判断:hash & oldCap
                        if ((e.hash & oldCap) == 0) { 
                            // 低位:原位置
                            if (loTail == null) loHead = e;
                            else loTail.next = e;
                            loTail = e;
                        } else {
                            // 高位:原位置 + oldCap
                            if (hiTail == null) hiHead = e;
                            else hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    
                    // 将两个新链表挂到新数组
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
  1. 为什么这个优化很重要?
    1.性能提升‌:避免了大量的 hash() 计算和取模运算,扩容速度显著提升。
    2.数据分布均匀‌:由于 hash 值的随机性,大约一半的数据留在原位,一半的数据迁移到 index + oldCap,保证了新数组负载因子的平衡。
    3.‌线程安全改进‌:虽然 HashMap 依然不是线程安全的,但这种‌保持顺序的尾插拆分‌彻底解决了 1.7 头插法导致的环形链表死循环问题。
  2. 面试高频追问
    ‌Q: 为什么 HashMap 容量必须是 2 的幂次方?‌
    A:
    1.为了高效取模‌:hash % length 等价于 hash & (length - 1),位运算比取模快得多。
    2.为了扩容优化‌:只有容量是 2 的幂,扩容时才满足「新下标 = 原下标 或 原下标+旧容量」的特性,才能实现上述的高效拆分。
    ‌Q: 如果初始容量传入 10,HashMap 怎么处理?‌
    A: 它会通过 tableSizeFor(initialCapacity) 方法,找到大于等于 10 的最小的 2 的幂,即 ‌16‌。
相关推荐
_Evan_Yao2 小时前
递归函数入门:以阶乘和斐波那契数列为例
python·学习·算法
DFT计算杂谈2 小时前
WannierTools输入文件wt.in一键批量生成脚本
java·前端·chrome·python·算法·conda
叫我:松哥2 小时前
基于卷积神经网络的人脸情绪识别算法,引入残差连接与SE注意力模块
人工智能·深度学习·神经网络·算法·cnn·迁移学习·图像识别
KaMeidebaby3 小时前
卡梅德生物技术快报|羊驼免疫:分子生物学实战:基于羊驼免疫的重链抗体制备与全流程验证方案
前端·网络·数据库·人工智能·算法·百度
oort1233 小时前
AI+基层治理·智慧政务解决方案——AI民意速办智能助手
大数据·人工智能·算法·政务
渡之3 小时前
GeoBridge 深度解析:语义锚定多视图基础模型,重塑无人机跨视角地理定位
深度学习·算法·动态规划·无人机
一口吃俩胖子3 小时前
【脉宽调制DCDC功率变换学习笔记024】电压反馈补偿和环路增益
笔记·学习·算法
洛水水3 小时前
【力扣100题】80.寻找旋转排序数组中的最小值
数据结构·算法·leetcode
ting94520003 小时前
VC Boom 技术架构与核心算法深度解
人工智能·算法·架构