🔗 Hash 扩容 - 交互式可视化

理解为什么哈希表需要扩容、常见的扩容策略及如何实现

🤔 为什么 Hash 需要扩容?

🔑 装载因子 (Load Factor)

装载因子 = 元素数 / 桶数。当装载因子过高时,碰撞概率急剧上升,查找效率从 O(1) 退化到 O(n)。Java HashMap 默认在 0.75 时触发扩容。

⚡ 保持性能

哈希表的核心优势是 O(1) 平均查找。扩容通过增加桶的数量,将元素重新分布,降低碰撞概率,保持性能稳定。

📊 时间换空间

扩容的 rehash 操作是一次性开销,但换来了长期稳定的查询性能。这是一种典型的"空间换时间"策略。

8
桶数量 (Buckets)
0
元素数量 (Items)
0.00
装载因子
0
冲突数
装载因子0%
🗂️ 哈希表桶 (Buckets) — 拉链法解决冲突
📜 操作日志:
> 哈希表已初始化,桶数: 8,装载因子阈值: 0.75

🎮 操作面板

0.75

📐 常见的 Hash 扩容方案对比

🔢 倍增扩容 最常用

每次扩容为原来的 2 倍newSize = oldSize × 2)。

Java HashMap、Go map、Python dict 等均采用此方案。优点是实现简单、扩容次数少、index = hash & (n-1) 位运算高效。

🔒 质数扩容 Redis 使用

每次扩容到下一个质数(8 → 17 → 37 → 79...)。

Redis dict 默认使用。质数模运算使哈希分布更均匀,减少聚集效应,但需要维护质数表。

📈 递增扩容 渐进式

每次扩容增加固定数量(如 +8+16),或者不一次性完成,而是分批迁移旧数据。

优点是避免单次扩容的停顿时间,Redis 和 Go 的 map 都采用渐进式 rehash。

🛠️ 扩容是如何实现的?

1

检测是否触发扩容

每次插入时检查 装载因子 = count / buckets >= 阈值(通常 0.75),超过阈值则触发扩容。

2

分配新桶数组

根据所选策略确定新大小(2x、下一个质数、+N),创建新的空桶数组。

3

遍历旧桶,逐元素 Rehash

遍历每个旧桶中的每个元素,用新的桶数重新计算 newIndex = hash(key) % newSize,插入到对应的新桶中。

4

替换 & 释放旧数组

将桶数组指针指向新数组,释放旧数组内存。如果是渐进式 rehash,则逐步完成迁移。