C++ 中的 buckets 详解:从哈希桶到 unordered_map 底层原理

在学习 C++ STL 容器时,我们经常会接触到 vectormapset 等容器。相比这些容器,unordered_mapunordered_set 这一类容器有一个非常重要的概念:bucket,也就是"桶"。

很多初学者第一次看到 bucket_count()bucket_size()load_factor()rehash() 这些函数时会比较疑惑:

这些 bucket 到底是什么?

它们和哈希表有什么关系?

为什么 unordered_map 的查询效率通常是 O(1)?

本文就来系统介绍 C++ 中的 buckets。


一、bucket 是什么?

在 C++ 中,bucket 通常翻译为

在 STL 的哈希容器中,例如:

复制代码
unordered_map
unordered_set
unordered_multimap
unordered_multiset

这些容器底层通常使用 哈希表 实现,而哈希表内部会把元素分布到若干个 bucket 中。

可以简单理解为:

复制代码
unordered_map 内部不是把所有数据放在一个大数组里,
而是先准备很多个桶,
每个元素通过哈希函数计算后,被放入某一个桶中。

例如我们有一个 unordered_map<int, string>

复制代码
unordered_map<int, string> mp;

mp[1] = "apple";
mp[2] = "banana";
mp[10] = "orange";
mp[15] = "pear";

这些 key 会经过哈希函数计算,然后被放入不同的桶中。

大致结构可以想象成:

复制代码
bucket 0: 
bucket 1: (1, apple)
bucket 2: (2, banana)
bucket 3:
bucket 4:
bucket 5: (10, orange) -> (15, pear)

注意:上面只是示意图,真实分布取决于哈希函数和桶的数量。


二、bucket 和哈希表的关系

unordered_mapunordered_set 的核心思想是 哈希表

哈希表的工作流程大致如下:

  1. 拿到一个 key;

  2. 使用哈希函数计算 key 的哈希值;

  3. 根据哈希值确定它应该放到哪个 bucket;

  4. 在对应的 bucket 中查找、插入或删除元素。

例如:

复制代码
bucket_index = hash(key) % bucket_count;

其中:

复制代码
hash(key)

表示 key 经过哈希函数得到的哈希值。

复制代码
bucket_count

表示当前哈希表中桶的数量。

最终通过取模运算,确定元素应该进入哪一个 bucket。


三、为什么 unordered_map 查询效率高?

普通的 map 底层通常是红黑树,查询、插入、删除的时间复杂度是:

复制代码
O(log n)

unordered_map 底层通常是哈希表,在哈希冲突较少的情况下,查询、插入、删除的平均时间复杂度是:

复制代码
O(1)

原因就在于 bucket。

假设我们要查找 key 为 100 的元素。

对于 map 来说,它需要在树结构中一层一层比较。

而对于 unordered_map 来说,它会先通过哈希函数直接定位到某个 bucket,然后只在这个 bucket 中查找。

也就是说:

复制代码
unordered_map 不需要从头到尾查找,
而是通过哈希值快速定位到目标桶。

这就是它平均效率很高的原因。


四、哈希冲突是什么?

虽然哈希表效率很高,但是有一个重要问题:哈希冲突

所谓哈希冲突,就是多个不同的 key 被分配到了同一个 bucket 中。

例如:

复制代码
key = 10 进入 bucket 3
key = 21 也进入 bucket 3
key = 37 也进入 bucket 3

这种情况就叫做哈希冲突。

示意图:

复制代码
bucket 0:
bucket 1:
bucket 2:
bucket 3: 10 -> 21 -> 37
bucket 4:

当多个元素都在同一个 bucket 中时,查找效率就会下降。

如果一个 bucket 中元素过多,那么查找时就需要在这个 bucket 内部继续遍历。

所以,哈希表的性能不仅取决于元素数量,也取决于 bucket 的数量和元素分布情况。


五、C++ 中与 bucket 相关的常用函数

C++ 的 unordered_mapunordered_set 提供了一些和 bucket 相关的成员函数。

下面以 unordered_map 为例介绍。


1. bucket_count()

bucket_count() 用来返回当前容器中的 bucket 数量。

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp[1] = "apple";
    mp[2] = "banana";
    mp[3] = "orange";

    cout << "当前 bucket 数量:" << mp.bucket_count() << endl;

    return 0;
}

输出结果可能是:

复制代码
当前 bucket 数量:13

注意,不同编译器、不同 STL 实现中,bucket 的数量可能不同。


2. max_bucket_count()

max_bucket_count() 用来返回容器理论上最多可以拥有的 bucket 数量。

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, int> mp;

    cout << "最大 bucket 数量:" << mp.max_bucket_count() << endl;

    return 0;
}

这个函数一般了解即可,实际开发中使用较少。


3. bucket_size(n)

bucket_size(n) 用来返回第 n 个 bucket 中有多少个元素。

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp[1] = "apple";
    mp[2] = "banana";
    mp[10] = "orange";
    mp[20] = "pear";

    for (size_t i = 0; i < mp.bucket_count(); i++) {
        cout << "bucket " << i << " 中的元素数量:"
             << mp.bucket_size(i) << endl;
    }

    return 0;
}

这个函数可以帮助我们观察哈希表中元素的分布情况。

如果很多元素都集中在少数几个 bucket 中,就说明哈希冲突比较严重。


4. bucket(key)

bucket(key) 用来返回某个 key 所在的 bucket 编号。

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp[1] = "apple";
    mp[2] = "banana";
    mp[10] = "orange";

    cout << "key = 1 所在的 bucket:" << mp.bucket(1) << endl;
    cout << "key = 2 所在的 bucket:" << mp.bucket(2) << endl;
    cout << "key = 10 所在的 bucket:" << mp.bucket(10) << endl;

    return 0;
}

这个函数可以用来查看某个元素被哈希到了哪个桶中。


5. load_factor()

load_factor() 表示当前哈希表的负载因子。

计算公式是:

复制代码
load_factor = 元素数量 / bucket 数量

也就是:

复制代码
load_factor = size() / bucket_count()

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp[1] = "apple";
    mp[2] = "banana";
    mp[3] = "orange";

    cout << "元素数量:" << mp.size() << endl;
    cout << "bucket 数量:" << mp.bucket_count() << endl;
    cout << "负载因子:" << mp.load_factor() << endl;

    return 0;
}

如果负载因子过高,说明平均每个 bucket 中存放的元素变多,哈希冲突可能会增加。


6. max_load_factor()

max_load_factor() 用来查看或设置最大负载因子。

当当前负载因子超过最大负载因子时,容器可能会自动扩容,也就是重新分配 bucket。

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    cout << "默认最大负载因子:" << mp.max_load_factor() << endl;

    mp.max_load_factor(0.7);

    cout << "修改后的最大负载因子:" << mp.max_load_factor() << endl;

    return 0;
}

最大负载因子越小,哈希冲突通常越少,但需要更多内存。

最大负载因子越大,占用内存可能更少,但哈希冲突可能更多。


7. rehash(n)

rehash(n) 用来设置 bucket 的数量至少为 n

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    cout << "初始 bucket 数量:" << mp.bucket_count() << endl;

    mp.rehash(100);

    cout << "rehash 后 bucket 数量:" << mp.bucket_count() << endl;

    return 0;
}

需要注意的是:

复制代码
mp.rehash(100);

并不一定表示 bucket 数量最终刚好是 100,只能保证 bucket 数量至少满足容器的要求。

不同编译器可能会选择不同的实际 bucket 数量。


8. reserve(n)

reserve(n) 用来提前为 n 个元素分配合适的 bucket 空间。

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp.reserve(1000);

    cout << "bucket 数量:" << mp.bucket_count() << endl;

    return 0;
}

如果我们提前知道要插入大量元素,使用 reserve() 可以减少扩容次数,提高性能。

例如:

复制代码
unordered_map<int, int> mp;
mp.reserve(100000);

for (int i = 0; i < 100000; i++) {
    mp[i] = i;
}

这样比直接插入更加稳定,因为可以减少频繁 rehash 的开销。


六、遍历 bucket 中的元素

unordered_map 还支持按 bucket 遍历元素。

可以使用:

复制代码
begin(bucket_index)
end(bucket_index)

示例:

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp[1] = "apple";
    mp[2] = "banana";
    mp[10] = "orange";
    mp[20] = "pear";
    mp[30] = "grape";

    for (size_t i = 0; i < mp.bucket_count(); i++) {
        cout << "bucket " << i << ": ";

        for (auto it = mp.begin(i); it != mp.end(i); it++) {
            cout << "(" << it->first << ", " << it->second << ") ";
        }

        cout << endl;
    }

    return 0;
}

这个代码会打印每个 bucket 中的元素。

通过这个方法,可以更直观地观察哈希表内部的元素分布。


七、完整示例:观察 unordered_map 的 bucket 分布

下面写一个完整程序,观察 unordered_map 的 bucket 变化。

复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<int, string> mp;

    mp[1] = "apple";
    mp[2] = "banana";
    mp[3] = "orange";
    mp[10] = "pear";
    mp[20] = "grape";
    mp[30] = "watermelon";

    cout << "元素数量 size: " << mp.size() << endl;
    cout << "bucket 数量 bucket_count: " << mp.bucket_count() << endl;
    cout << "负载因子 load_factor: " << mp.load_factor() << endl;
    cout << "最大负载因子 max_load_factor: " << mp.max_load_factor() << endl;

    cout << endl;
    cout << "每个 key 所在的 bucket:" << endl;

    for (auto &p : mp) {
        cout << "key = " << p.first
             << ", value = " << p.second
             << ", bucket = " << mp.bucket(p.first)
             << endl;
    }

    cout << endl;
    cout << "每个 bucket 中的元素:" << endl;

    for (size_t i = 0; i < mp.bucket_count(); i++) {
        cout << "bucket " << i << ": ";

        for (auto it = mp.begin(i); it != mp.end(i); it++) {
            cout << "(" << it->first << ", " << it->second << ") ";
        }

        cout << endl;
    }

    return 0;
}

这段代码可以帮助我们理解:

复制代码
1. unordered_map 内部有多少个 bucket;
2. 每个元素被放到了哪个 bucket;
3. 每个 bucket 中有多少元素;
4. 哈希表负载因子的变化。

八、bucket 过多或过少会有什么影响?

bucket 的数量会直接影响哈希表的性能和内存占用。

1. bucket 太少

如果 bucket 数量太少,很多元素就会被放入同一个 bucket 中。

这样会导致哈希冲突增多。

例如:

复制代码
bucket 0: 1 -> 5 -> 10 -> 20 -> 31 -> 42
bucket 1:
bucket 2:

这种情况下,虽然使用的是 unordered_map,但是查询时可能需要在一个很长的链表或结构中查找,效率会下降。

极端情况下,时间复杂度可能退化到:

复制代码
O(n)

2. bucket 太多

如果 bucket 数量太多,虽然哈希冲突会减少,但是会占用更多内存。

例如只有 10 个元素,却分配了 10000 个 bucket,这就会造成空间浪费。

所以 bucket 数量不是越多越好,而是要根据元素数量合理分配。


九、rehash 和 reserve 的区别

很多人容易混淆 rehash()reserve()

它们都可以影响 bucket 数量,但含义不同。

1. rehash(n)

rehash(n) 的意思是:

复制代码
设置 bucket 的数量至少为 n。

例如:

复制代码
mp.rehash(100);

表示希望哈希表至少有 100 个 bucket。

2. reserve(n)

reserve(n) 的意思是:

复制代码
为至少能容纳 n 个元素提前分配空间。

例如:

复制代码
mp.reserve(100);

表示希望容器能够比较高效地存放 100 个元素。

它会根据当前的最大负载因子自动计算需要多少 bucket。

可以简单理解为:

复制代码
rehash 管的是 bucket 数量;
reserve 管的是元素容量。

实际开发中,如果提前知道要插入多少个元素,通常更推荐使用:

复制代码
reserve()

例如:

复制代码
unordered_map<int, int> mp;
mp.reserve(100000);

这样代码含义更明确。


十、bucket 和 unordered_set

unordered_set 中也有 bucket 的概念。

示例:

复制代码
#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_set<int> s;

    s.insert(10);
    s.insert(20);
    s.insert(30);
    s.insert(40);

    cout << "元素数量:" << s.size() << endl;
    cout << "bucket 数量:" << s.bucket_count() << endl;

    for (int x : s) {
        cout << "元素 " << x << " 在 bucket "
             << s.bucket(x) << endl;
    }

    return 0;
}

unordered_set 只存储 key,而 unordered_map 存储的是 key-value 键值对。

但是它们底层都依赖哈希表和 bucket。


十一、bucket 和桶排序中的 bucket

除了 STL 哈希容器中的 bucket,算法中也有一个经典概念叫 桶排序 ,英文是 Bucket Sort

桶排序的思想是:

复制代码
把数据按照一定规则分到不同的桶中,
每个桶内部再进行排序,
最后按桶的顺序把结果合并起来。

例如要排序下面这些数:

复制代码
29, 25, 3, 49, 9, 37, 21, 43

可以按照范围分桶:

复制代码
0~9:   3, 9
10~19:
20~29: 29, 25, 21
30~39: 37
40~49: 49, 43

然后每个桶内部排序:

复制代码
0~9:   3, 9
20~29: 21, 25, 29
30~39: 37
40~49: 43, 49

最后合并:

复制代码
3, 9, 21, 25, 29, 37, 43, 49

桶排序适合数据分布比较均匀的情况。


十二、桶排序 C++ 示例

下面给出一个简单的桶排序代码:

复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void bucketSort(vector<int>& nums) {
    if (nums.empty()) return;

    int maxVal = *max_element(nums.begin(), nums.end());
    int minVal = *min_element(nums.begin(), nums.end());

    int bucketCount = 5;
    vector<vector<int>> buckets(bucketCount);

    int range = maxVal - minVal + 1;

    for (int num : nums) {
        int index = (num - minVal) * bucketCount / range;
        buckets[index].push_back(num);
    }

    nums.clear();

    for (int i = 0; i < bucketCount; i++) {
        sort(buckets[i].begin(), buckets[i].end());

        for (int num : buckets[i]) {
            nums.push_back(num);
        }
    }
}

int main() {
    vector<int> nums = {29, 25, 3, 49, 9, 37, 21, 43};

    bucketSort(nums);

    for (int x : nums) {
        cout << x << " ";
    }

    return 0;
}

输出结果:

复制代码
3 9 21 25 29 37 43 49

这里的 bucket 是算法意义上的"桶",和 unordered_map 中的 bucket 思想类似,都是将数据分组管理。

不过二者的使用场景不同:

复制代码
unordered_map 中的 bucket:用于哈希表存储和查找;
桶排序中的 bucket:用于排序算法中的分组。

十三、实际开发中需要关心 bucket 吗?

大多数情况下,我们使用 unordered_mapunordered_set 时,不需要手动管理 bucket。

例如:

复制代码
unordered_map<string, int> mp;

mp["apple"] = 10;
mp["banana"] = 20;
mp["orange"] = 30;

正常写代码即可,STL 会自动帮我们管理 bucket。

但是在以下情况中,了解 bucket 很有用:

复制代码
1. 需要优化 unordered_map 的性能;
2. 需要插入大量数据;
3. 需要分析哈希冲突;
4. 需要理解 unordered_map 的底层原理;
5. 面试中被问到哈希表实现原理。

如果提前知道数据量很大,可以使用:

复制代码
unordered_map<int, int> mp;
mp.reserve(100000);

这样可以减少扩容和 rehash,提高插入效率。


十四、面试中如何解释 bucket?

如果面试官问:

复制代码
unordered_map 中的 bucket 是什么?

可以这样回答:

复制代码
unordered_map 底层通常使用哈希表实现。哈希表内部会维护若干个 bucket,也就是桶。
当插入一个 key-value 时,会先通过哈希函数计算 key 的哈希值,然后根据哈希值映射到某个 bucket 中。
如果多个 key 被映射到同一个 bucket,就会产生哈希冲突。
bucket 的数量和负载因子会影响 unordered_map 的性能。
当元素数量增加导致负载因子过高时,容器可能会进行 rehash,增加 bucket 数量,并重新分布元素。

这个回答基本可以覆盖核心点。


十五、常见函数总结

函数 作用
bucket_count() 返回当前 bucket 数量
max_bucket_count() 返回最大 bucket 数量
bucket_size(n) 返回第 n 个 bucket 中的元素数量
bucket(key) 返回 key 所在的 bucket 编号
load_factor() 返回当前负载因子
max_load_factor() 获取或设置最大负载因子
rehash(n) 设置 bucket 数量至少为 n
reserve(n) 为容纳 n 个元素提前分配空间
begin(n) 返回第 n 个 bucket 的起始迭代器
end(n) 返回第 n 个 bucket 的结束迭代器

十六、总结

bucket 是理解 C++ 哈希容器的重要概念。

unordered_mapunordered_set 等容器中,bucket 用来存放经过哈希函数映射后的元素。每个元素会根据 key 的哈希值进入某个 bucket。

合理的 bucket 数量可以减少哈希冲突,提高查询、插入和删除效率。

相关推荐
计算机安禾3 小时前
【c++面向对象编程】第35篇:构造函数与异常:如何避免资源泄露?
开发语言·javascript·c++·算法·性能优化
z200509303 小时前
今日算法(二叉树剪枝)
数据结构·c++·算法·剪枝
IronMurphy3 小时前
【算法四十八】416. 分割等和子集
算法
WYH2873 小时前
C语言结构体变量和结构体指针详解:定义、访问、传参与易错点总结
c语言·开发语言·算法
m0_748839494 小时前
利用C 图形界面展示MATLAB算法的高效混合编程实践
开发语言·算法·matlab
进击的荆棘4 小时前
优选算法——哈希表
c++·算法·leetcode·哈希算法·散列表
阿牛大牛中4 小时前
阿里-RecGPT-Mobile
大数据·人工智能·算法
RH2312114 小时前
2026.5.17数据结构 八大排序
数据结构·算法·排序算法
加号34 小时前
【C#】 实现 CRC16 校验:原理、算法与工程实践
算法·c#