卡码笔记
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
  • 基础与语法

  • 面向对象

  • STL 与容器

    • STL容器了解哪些
    • STL中allocator的作用
    • STL中迭代器失效的场景
    • c++的map和unordered_map有什么区别和实现原理
    • map,deque,list的底层实现原理
    • unordered_map的rehash机制
      • 简要回答
      • 详细回答
      • 代码示例
      • 知识拓展
    • vector底层原理和扩容过程
    • push_back()和emplace_back()的区别
  • 内存管理

  • C++11 与现代 C++

  • 智能指针

  • 并发与 I/O

# c++中 unordered_map的负载因子与rehash机制

面试时没有回答好问题是很正常的,被面试官质疑时,要有今天不会明天会的底气,面后做好复盘,不在同一道题目上跌倒第二次,今天就从unordered_map的负载因子与rehash机制背起来

# 简要回答

unordered_map的负载因子是已存储元素数量与桶数组大小的比值。

当负载因子超过预设的最大负载因子时,容器会自动执行 rehash:创建一个新的、更大的桶数组,并将所有现有元素重新哈希到新的数组中,以降低每个桶的平均元素数,从而保证 O(1) 平均时间复杂度的操作性能。

# 详细回答

负载因子:

定义:负载因子是衡量哈希表拥挤程度的指标。

其计算公式为:负载因子 = size() / bucket_count()。

其中 size()是容器中键值对的数量,bucket_count()是底层桶数组的大小。

作用:负载因子直接影响了哈希表的性能。

负载因子越高,意味着平均每个桶中存储的元素越多(即哈希冲突更严重)。

这会导致查找、插入等操作需要遍历更长的链表(或更深的树结构),从而性能下降。

最大负载因子:

unordered_map有一个默认的最大负载因子(通常为 1.0)。

当前负载因子 > 最大负载因子时,容器会自动触发 rehash 操作,以维持操作的效率。

Rehash 机制:

触发条件:有两种,分别为自动触发和手动触发。

自动触发:在插入新元素后,如果计算出的新负载因子超过了 max_load_factor(),容器会自动 rehash。

手动触发:程序员可以主动调用 rehash(n)或 reserve(m)函数。

rehash(n):将桶数量设置为至少为 n。

如果 n大于 size() / max_load_factor(),则新的 bucket_count()将 >= n。

reserve(m):预留空间,确保在存入 m个元素前不会 rehash。

它内部会计算需要多少桶:rehash(ceil(m / max_load_factor()))。

过程:

分配一块新的、更大的内存空间作为新的桶数组。

遍历旧桶数组中的所有元素(包括每个桶链表/树上的所有元素)。

根据元素键的新哈希值和新桶数组的大小,重新计算每个元素应该归属的新桶的位置。

将元素移动到新的桶中,释放旧桶数组的内存。

特点:

Rehash 是一个相对耗时的操作,时间复杂度为 O(n),其中 n 是容器中元素的数量。

它还会使所有迭代器失效(但指向元素本身的引用和指针不会失效)。

# 代码示例

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> myMap;

    // 1. 查看默认的桶数量、最大负载因子
    std::cout << "初始桶数量: " << myMap.bucket_count() << std::endl;
    std::cout << "最大负载因子: " << myMap.max_load_factor() << std::endl; // 通常为 1.0

    // 2. 插入元素,观察负载因子变化和 rehash
    for (int i = 0; i < 1000; ++i) {
        // 在插入前检查,如果负载因子接近最大值,可能会触发rehash
        if (myMap.load_factor() > 0.8) { // 检查当前负载因子
            std::cout << "即将触发rehash... ";
            std::cout << "Size: " << myMap.size();
            std::cout << ", Buckets: " << myMap.bucket_count();
            std::cout << ", Load Factor: " << myMap.load_factor() << std::endl;
        }
        myMap["key" + std::to_string(i)] = i;
    }

    std::cout << "最终桶数量: " << myMap.bucket_count() << std::endl;
    std::cout << "最终负载因子: " << myMap.load_factor() << std::endl;

    // 3. 手动控制 rehash
    std::cout << "\n--- 手动 rehash ---" << std::endl;
    std::unordered_map<std::string, int> myMap2;

    // 在插入大量数据前,先预留空间,避免多次自动rehash
    myMap2.reserve(1000); // 预留1000个元素的空间,内部会自动计算所需的桶数并rehash
    std::cout << "调用 reserve(1000) 后的桶数量: " << myMap2.bucket_count() << std::endl;

    // 或者直接指定桶的数量
    myMap2.rehash(2048);
    std::cout << "调用 rehash(2048) 后的桶数量: " << myMap2.bucket_count() << std::endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 知识拓展

  • 知识图解

image

  • 适用场景

适用 unordered_map: 对单个元素的存取速度要求极高,不需要元素按键排序。

能够提供良好的哈希函数来分散键的分布。

可以接受 rehash 带来的偶尔性能波动和迭代器失效。

不适用 unordered_map: 需要元素按键排序,需要稳定的性能,无法接受 rehash 带来的延迟峰值(如实时系统)。

内存非常紧张,因为 unordered_map通常会有一些空桶以保持低负载因子。

键的类型没有合适的哈希函数或哈希函数质量很差。

  • 面试官可能追问

Q1:rehash 的复杂度是多少?为什么?

A1:O(n),其中 n 是元素数量。因为需要遍历每个元素,重新计算哈希并插入到新桶中。

Q2:reserve和 rehash有什么区别?

A2:reserve(n)是从元素数量的角度出发,保证在存入 n 个元素前不会 rehash。它内部会计算需要的桶数。rehash(n)是直接从桶数量的角度出发,设置桶的数量至少为 n。通常更推荐使用 reserve,因为它的语义(预留 n 个元素的空间)更直观。

Q3:rehash 后,迭代器、引用、指针会失效吗?

A3迭代器会全部失效,因为元素已经被移动到了新的内存地址。但是,元素的引用和指针不会失效。因为 rehash 过程是移动元素(节点),而不是复制或改变元素本身在内存中的地址(在常见的节点式实现中)。

Q3除了负载因子,还有什么情况会触发 rehash?

A3:手动调用 rehash()或 reserve()函数。此外,某些操作如 clear()后,实现可能会选择缩小桶数组(但标准不要求),这也可以看作一种 rehash。

Last Updated: 3/10/2026, 6:08:48 PM

← map,deque,list的底层实现原理 vector底层原理和扩容过程 →

评论

验证登录状态...

侧边栏
夜间
卡码简历
代码随想录
卡码投递表🔥
2026群
添加客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记
鼓励/支持/赞赏Carl
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪