‌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‌。
相关推荐
HjhIron2 小时前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩3 小时前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹4 小时前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术9 小时前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc
浮生望10 小时前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
黄敬峰10 小时前
面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法
算法
地平线开发者20 小时前
J6B vio scenario sample
算法
BothSavage1 天前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn1 天前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法