std::unordered_map扩容由load factor触发,即size()/bucket_count()>max_load_factor()时立即全量rehash;桶数默认为质数以降低冲突,reserve()和rehash()可预分配桶但行为不同。

扩容触发条件:load factor 超过 max_load_factor()
std::unordered_map 扩容不是按插入元素个数,而是看当前 load factor = size() / bucket_count() 是否超过 max_load_factor()。默认值是 1.0,但可调:umap.max_load_factor(0.75)。一旦插入后触发检查(比如 insert() 或 emplace()),且 size() > bucket_count() * max_load_factor(),就会立即 rehash。
注意:rehash 是全量重建——新桶数组分配、所有节点重新 hash、逐个迁移。没有渐进式扩容,所以单次插入可能耗时突增。
- 调用
reserve(n)会直接把bucket_count()设为不小于n的最小质数,同时隐式调整max_load_factor()不变,但能推迟扩容 -
rehash(n)强制重设桶数量为不小于n的最小质数,不改变max_load_factor() - 某些 libstdc++ 实现中,空 map 初始
bucket_count()是 11;libc++ 可能是 1 —— 实现依赖,不能假设
桶数量为什么总是质数?
标准库实现(如 GCC 的 libstdc++)强制桶数组长度为质数,是为了降低哈希冲突概率。它用的是 hash(key) % bucket_count() 做桶索引,当 bucket_count() 是质数时,只要哈希函数输出分布尚可,模运算后更易打散。
这不是标准强制要求,但主流实现都这么做。你可以通过 umap.bucket_count() 观察实际值,会发现每次扩容后都是质数(如 11 → 23 → 47 → 97…)。
立即学习“C++免费学习笔记(深入)”;
- 质数序列由内部静态表提供,不现场计算,避免开销
- 若手动
rehash(100),实际分配的桶数可能是 101(下一个质数) - 如果自己实现哈希表并用 2 的幂次桶数,需改用
hash(key) & (bucket_count()-1),但 std::unordered_map 不支持
insert/emplace 后 size() 和 bucket_count() 的变化时机
插入操作返回的 pair<iterator bool></iterator> 中的 bool 仅表示“是否新增”,不反映是否发生了 rehash。rehash 发生在插入逻辑末尾、返回前,因此 size() 和 bucket_count() 在插入返回后已是最新值。
这意味着:如果你在循环中反复 insert() 并依赖 bucket_count() 做性能判断,要意识到某次调用可能突然让 bucket_count() 翻倍(或跳到下一个质数),而前几次都毫无征兆。
- 不要在 tight loop 中频繁查
bucket_count()来做分支逻辑,cache 不友好且无实质收益 -
emplace()和insert()在扩容行为上完全一致,区别只在构造方式 - 移动语义(如
std::move(key))不影响扩容逻辑,只影响节点构造开销
自定义哈希与装载因子配合的常见陷阱
如果你写了自定义哈希函数,但没同步考虑 max_load_factor(),很容易误判性能。例如:一个糟糕的哈希(大量碰撞)会让实际平均链长远超 1,即使 load factor 还不到 0.5,查找已退化为 O(n)。
此时调低 max_load_factor()(比如设成 0.5)并不能根本解决问题——桶数变多,但每个桶里还是挤着一堆同 hash 值的元素。
- 真正该做的是检查
umap.bucket_size(i)分布:用for (size_t i = 0; i 遍历,看是否有极端偏斜 - 装载因子只是桶利用率的粗略指标,无法反映哈希质量
- gcc 的
__gnu_cxx::hash_set(已废弃)曾允许非质数桶数,但std::unordered_map没有这种灵活性
桶数量不可控、质数序列不可配、rehash 不可暂停——这些不是 bug,是标准库为通用场景做的取舍。真要确定性行为,得换用 robin-hood hash 或 ska_hash_map 这类第三方实现。


















