【面试题】说说HashMap是如何进行扩容的?

HashMap在数组满时会两倍扩容,扩容过程中通过rehash将原数组元素迁移到新数组。在16到32的扩容例子中,由于新n-1的高位多一个1,导致某些元素位置改变。rehash通过位运算提高效率,避免使用取模操作。扩容时,如果桶单元是链表或红黑树,会根据与旧容量的运算决定在新桶的位置。

HashMap扩容:两倍扩容,底层为数组,当数组满了之后,会自动扩容。

扩容:扩大数组长度,对原数组进行rehash操作,把原数组copy到新数组中

问题:假设16数组中[<>]同一个位置存放了多对key-value,当扩容为32位时,其中某对或多对key-value发生了位置的变动。

扩容后,要进行rehash重新将hash值与n-1进行与运算。如下

数组长度=16

n-1 0000 0000 0000 0000 0000 0000 0000 1111

hash1 1111 1111 1111 1111 0000 1111 0000 0101

&结果 0000 0000 0000 0000 0000 0000 0000 0101

&结果=5(index为5的位置)

n-1 0000 0000 0000 0000 0000 0000 0000 1111

hash2 1111 1111 1111 1111 0000 1111 0001 0101

&结果 0000 0000 0000 0000 0000 0000 0000 0101

&结果=5(index为5的位置)

在数组长度为16的时候,两个hash值的位置是一样的,用链表来进行处理,出现一个hash冲突的问题

如果数组长度扩容之后为32,重新对每个hash值进行寻址,也就是用每个hash值跟新数组的length-1进行与操作

数组长度=32

n-1 0000 0000 0000 0000 0000 0000 0001 1111

hash1 1111

<think>我们刚刚讨论过HashMap的数据结构(数组+链表+红黑树),现在用户聚焦在扩容机制上。根据引用内容,用户已经明确要求了解扩容机制如何工作,且引用中提供了关键信息:触发条件(size>threshold)、扩容步骤(2倍扩容、rehash)、以及优化(高低位拆分)。需要将这些信息整合成逻辑清晰的解释。 用户可能已经了解基础概念(如负载因子),所以直接深入机制细节,但避免过度深入源码。重点突出: 1. 触发条件(数学公式明确阈值计算) 2. 扩容过程(数组翻倍、数据迁移) 3. 优化点(高位运算如何避免重新哈希) 同时注意引用标记:引用[1][2][3][4]都与扩容相关,需在对应解释处标注。例如阈值计算公式来自引用[4],扩容优化参考引用[3]。 最后生成相关问题要围绕扩容机制延伸(如为什么是2倍扩容、负载因子影响等)。</think>### Java HashMap 扩容机制详解 HashMap扩容机制是其性能优化的核心设计,通过动态调整桶数组大小平衡哈希冲突与内存消耗。以下是扩容机制的完整工作流程(基于 Java 8+): --- #### 一、扩容触发条件 1. **核心判定公式**: $$ \text{size} > \text{capacity} \times \text{loadFactor} $$ - `size`:当前键值对数量 - `capacity`:桶数组长度(默认 16) - `loadFactor`:负载因子(默认 0.75) **示例**:默认桶数组长度 16 → 扩容阈值 $16 \times 0.75 = 12$,当元素超过 12 时触发扩容[^4]。 2. **树化联动触发**: - 当链表长度 ≥ 8 但桶数组长度 < 64 时,**优先扩容代替树化**(避免过早树化带来的开销)[^2]。 --- #### 二、扩容具体步骤 1. **创建新桶数组**: - 新容量 = 旧容量 × 2(如 16 → 32) - 新扩容阈值 = 新容量 × 负载因子(如 32 × 0.75 = 24)[^1]。 2. **数据迁移(Rehashing)**: 遍历旧数组的每个桶,对每个节点: - **计算新位置**: $$ \text{新下标} = \text{hash} \ \& \ (\text{新容量} - 1) $$ - **高低位拆分优化**(避免重新计算哈希): - 若 `(hash & oldCap) == 0` → 节点留在**低位区**(原下标位置) - 否则迁移到**高位区**(原下标 + 旧容量位置) *例如*:旧容量 16(二进制 10000),若 hash 第 5 位为 1 则迁移到高位[^3]。 3. **链表/树拆分**: - 链表:根据高位判断拆分成两个链表 - 红黑树:拆分后若节点数 ≤ 6 则退化为链表 4. **释放旧数组**: 完成迁移后,旧数组被垃圾回收。 --- #### 三、性能优化设计 | **机制** | **作用** | |-------------------|--------------------------------------------------------------------------| | **2倍扩容** | 保持桶长度为 2 的幂次方,使 `hash & (capacity-1)` 等效取模运算,加速定位 | | **高低位拆分** | 直接利用原哈希值计算新位置,避免重新哈希(时间复杂度 $O(n)$→$O(1)$/节点)[^3] | | **树化联动扩容** | 桶数量不足时优先扩容而非树化,减少红黑树构建开销 | --- #### 四、扩容开销与规避建议 1. **开销来源**: - 数据迁移需遍历所有节点(时间复杂度 $O(n)$) - 频繁扩容影响写入性能(如初始容量过小) 2. **优化建议**: - **预分配容量**:若预期存储 100 个元素,初始化时设置 `new HashMap<>(128)`($128 \times 0.75 > 100$) - **调整负载因子**:对内存敏感场景可增大负载因子(如 0.9),但会增加哈希碰撞概率[^1]。 --- ### 原理示意图 ``` 旧桶数组 (容量=8) 新桶数组 (容量=16) [0] → A → B [0] → A // 低位区 [4] → C → D [4] → C // 低位区 [8] → B // 高位区 (原下标0 + 旧容量8) [12]→ D // 高位区 (原下标4 + 旧容量8) ``` > 说明:节点 B 的 `hash & 8 !=0` 迁移到高位区,避免重新计算哈希值。 --- ### 相关问题 1. 为什么 HashMap 的桶数组长度必须是 2 的幂次方? 2. 负载因子设为 0.75 的数学依据是什么? 3. 高低位拆分优化如何减少扩容时的计算开销? 4. 在多线程环境下扩容会导致什么问题? 5. 红黑树在扩容后一定会退化成链表吗? [^1]: HashMap 扩容机制详解与优化建议 [^2]: 扩容与树化机制的协同设计原理 [^3]: 高低位拆分优化实现高效 Rehashing [^4]: 扩容阈值计算与触发条件解析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值