哈希表扩容由装载因子决定,Python dict默认≥2/3时触发;建议阈值设为0.7,在__setitem__末尾判断;扩容需新建数组并用新容量重哈希所有键,链地址法更易实现且安全。

哈希表扩容的触发条件怎么判断?
扩容不是“用得差不多了就扩”,而是由装载因子(load factor)决定的。装载因子 = 当前元素数量 / 底层数组长度。Python 的 dict 默认在装载因子 ≥ 2/3 时触发扩容,这是经过权衡空间与冲突概率后的经验值。
- 装载因子太小:浪费内存,频繁分配新数组
- 装载因子太大:链地址法下冲突激增,
<strong>getitem</strong>平均时间退化为 O(n) - 实际实现中建议设阈值为
0.7,比 Python 原生略保守,更易观察扩容行为
判断逻辑写在 __setitem__ 末尾即可:
if self.size >= int(self.capacity * 0.7):<br> self._resize(self.capacity * 2)
扩容时如何安全地重哈希所有已有键?
不能边遍历旧数组边往新数组塞——因为 <strong>setitem</strong> 可能再次触发扩容,导致无限递归。必须先建好新数组,再逐个取旧桶中的键值对,用新容量重新计算索引。
- 旧数组中每个桶可能是
None、单个键值对(无冲突),或链表节点(有冲突),需统一遍历 - 重哈希必须用新容量算索引:
hash(key) % new_capacity,不是复用旧索引 - 如果键类型不可哈希(如 list),
hash(key)会抛TypeError,这和原生dict行为一致,无需额外拦截
典型重哈希循环:
for bucket in self._buckets:<br> if bucket is not None:<br> # 处理单元素或链表<br> for k, v in bucket.items(): # 假设 bucket 是 dict 或可迭代结构<br> new_index = hash(k) % new_capacity<br> # 插入新桶,注意处理冲突
立即学习“Python免费学习笔记(深入)”;
为什么链地址法比开放寻址更适合手动扩容?
开放寻址(如线性探测)在扩容后无法直接迁移——旧索引位置上的元素,在新数组里可能因探测序列变化而“找不到家”。你得重新执行完整插入逻辑,且必须保证探测顺序一致(比如都用线性探测+相同步长),否则数据丢失。
- 链地址法天然解耦:每个键的存储位置只取决于
hash(key) % capacity,和邻近元素无关 - 扩容时只需按新容量重算一次索引,然后追加到对应链表尾部即可
- 冲突处理逻辑(如链表 or 红黑树升级)不随扩容改变,代码边界清晰
如果你硬要用开放寻址,必须把探测函数(如 _probe)抽成独立方法,并在扩容后对每个键调用它找新位置——这实际增加了出错概率,不推荐初学实现。
扩容后老引用还有效吗?
无效。扩容后底层数组被替换,所有基于旧索引的缓存(比如你手写的 _get_bucket_index 返回值)全部作废。
- 不要缓存
hash(key) % capacity的结果到实例变量里 - 不要在外部保存桶对象引用(如
bucket = ht._buckets[i]),扩容后该引用指向的内存可能已被回收或复用 - 迭代器(
<strong>iter</strong>)必须每次调用都实时访问当前self._buckets,不能预存快照
最容易忽略的一点:如果实现了 keys() / values() 返回列表,它们是扩容前的快照;但 items() 迭代器若内部缓存了桶指针,就会在扩容后 yield 错误数据。务必每次 next() 都查最新数组。


















