装填因子是散列表中已存元素个数n与桶数组长度m的比值α=n/m,反映表的填充程度而非内存占用率;java hashmap默认设为0.75,是在查找效率与空间利用率间实测权衡的结果。

装填因子是什么:一个比值,不是百分比
装填因子(Load Factor)是散列表中已存元素个数 n 与桶数组长度 m 的比值,即 α = n / m。它直接反映“表有多满”,但不是“用了多少内存”——因为每个桶只存一个指针或引用,实际空间还取决于元素本身大小。
常见误解是把它当成内存占用率。比如 α = 0.75 并不意味 75% 的内存被占了,而是平均每个桶连上 0.75 个元素(链地址法下),或有 75% 的桶非空(开放寻址下)。
为什么 0.75 是 Java HashMap 的默认阈值
这个值是实测权衡的结果:太低浪费空间,太高拖慢查找。当 α > 0.75 时,链地址法的平均查找长度会明显上升;开放寻址法的冲突概率也急剧增加(比如线性探测下,α = 0.9 时平均探查次数可能翻倍)。
-
α = 0.5:查找快,但空间利用率只有一半,扩容频繁 -
α = 0.9:空间省了,但哈希冲突激增,get()和put()常要遍历多个节点或多次探测 - Java 选
0.75是在典型负载下,让平均查找长度控制在 1~2 次之间,同时避免过早扩容
扩容时的装填因子陷阱:不是所有实现都重算 α
扩容后,新桶数组长度翻倍,但 n 不变,所以 α 立刻减半。问题在于:有些自定义散列表会在扩容后继续用旧 α 判断是否再扩容,导致误判;另一些则在插入前才检查,可能一次插入触发连续多次扩容。
容易踩的坑:
- 手动实现时,忘了在
resize()后重置统计变量n或误用旧m计算α - 用开放寻址法时,
α超过 0.9 就极难找到空位,insert()可能无限循环,必须设硬上限(如0.95)强制报错或拒绝插入 - 并发场景下,多个线程同时判断
α并触发扩容,可能重复建新表——ConcurrentHashMap 用分段锁或 CAS + 协作扩容来规避
不同语言/库对装填因子的控制方式差异
不是所有散列表都暴露 loadFactor 参数,也不是都能调。Python 的 dict 不允许设,内部用动态策略(当 α > 2/3 且表较大时扩容);Go 的 map 完全隐藏该参数,编译器决定扩容时机;而 C++ std::unordered_map 允许用 max_load_factor() 设置,并用 rehash() 手动干预。
关键区别:
- Java
HashMap(int initialCapacity, float loadFactor):loadFactor是触发扩容的阈值,不是目标值 - C++
max_load_factor():是“不允许超过”的上限,超了就自动rehash() - Python 不提供接口,但
sys.getsizeof(dict)可看出底层桶数组远大于元素数,说明预留了空间
改 loadFactor 不能解决哈希函数差的问题——如果大量键映射到同一桶,α 再低也没用。真正影响性能的是“分布均匀性”,不是“填得满不满”。










