‌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‌。
相关推荐
ZhengEnCi7 小时前
Agent 架构怎么选:从单 Agent 到多 Agent 编排的选型决策指南
算法·架构
秋天的怀念_David7 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Pniubi8 小时前
力扣283移动零(双指针法)
算法·leetcode·职场和发展
hhb_6188 小时前
大模型落地三重优化实战
人工智能·算法·机器学习
云和数据.ChenGuang8 小时前
光哥说Python RAG 系统需要哪些评估指标:从检索到生成的全链路评测指南
网络·人工智能·python·算法·langchain
2601_9707038310 小时前
工业除湿机租赁:把一份租金合同拆开看
大数据·数据结构·算法·数据库架构·推荐算法
心中有你021410 小时前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
ZhengEnCi11 小时前
同样的模型,为什么本地和云 API 差别那么大:量化精度、聊天模板与采样参数的三大元凶
人工智能·算法·llm
Flynn Liang11 小时前
RAG 答非所问/幻觉怎么破?Spring AI QuestionAnswerAdvisor 召回优化实战(TopK/重排/混合检索)
人工智能·算法·spring·rag·幻觉·springai
liminla!11 小时前
SQL 从入门到进阶:语法速查与实战练习(完整优化版)
数据库·sql·算法