‌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‌。
相关推荐
INGNIGHT3 小时前
270 · 电话号码的字母组合II(Trie)
linux·算法
2401_839080543 小时前
C++常见八股
数据结构·c++·算法
青山是哪个青山3 小时前
LeetCode 188:买卖股票的最佳时机 IV
算法
mikuyyds4 小时前
geo-toolbox 插件算法解析:RUSLE 与 MUSLE 的工程化落地
算法·rust·gis
a187927218314 小时前
【算法】树(二):队列与祖先——BFS 骨架三配件、短路透传与合流
算法·leetcode·二叉树··bfs·算法讲解·树遍历
syagain_zsx4 小时前
算法基础篇 · 03 枚举(C++ 题解)
c++·算法·二进制·枚举
weixin_307779135 小时前
C++代码实现MATLAB中的ode23tb函数功能
开发语言·c++·算法·matlab
我是章汕呐5 小时前
地级市能源消耗量及消耗强度数据【2006-2023年】平衡面板
人工智能·经验分享·算法·回归
charliejohn6 小时前
计算机考研 408 数据结构 堆的插入与删除 堆排序
算法
老鱼说AI6 小时前
从点积到希尔伯特空间:向量内积的几何本质与大模型相似度度量
人工智能·深度学习·线性代数·算法·机器学习·数学建模