跳到主要内容
版本:Next

unordered_map

std::unordered_map 是 C++11 引入的无序关联容器,用来存储“键值对”。它基于哈希表实现,核心目标是让查找、插入、删除在平均情况下接近常数时间。

std::map 相比:

  1. unordered_map 不保证按键有序。
  2. 典型操作平均复杂度更低(平均 O(1))。
  3. 不能做基于顺序的范围查找(如 lower_bound)。

头文件与基本特征

#include <unordered_map>

常见特征如下:

特征说明
元素类型std::pair<const Key, T>
顺序无序(迭代顺序不稳定)
底层结构哈希表(桶 + 链/节点)
查找复杂度平均 O(1),最坏 O(n)
键是否重复不允许重复键

基本用法

#include <iostream>
#include <string>
#include <unordered_map>

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

scores["Alice"] = 95;
scores["Bob"] = 88;
scores["Cathy"] = 91;

std::cout << "Alice = " << scores["Alice"] << '\n';
return 0;
}

operator[] 在键不存在时会自动插入默认值,再返回该值引用。

交互演示(MDX + React)

下面这个面板会模拟 unordered_map 的桶分布和常见操作,适合快速理解:

  1. insert / operator[] 更新行为。
  2. finderase 的查询删除路径。
  3. reservebucket_countload_factor 的变化。
  4. 冲突时同一桶内挂载多个键值对。

unordered_map 哈希桶交互演示

观察 insert / find / erase 与 bucket_count、load_factor、冲突桶分布的变化。

MDX + React
size()3
bucket_count()5
load_factor()0.60
non-empty buckets3

bucket #0

empty

bucket #1

empty

bucket #2

  • cat: 7

bucket #3

  • banana: 5

bucket #4

  • apple: 3

最近操作

  • 初始化: 已插入 apple/banana/cat

遍历

#include <iostream>
#include <string>
#include <unordered_map>

int main() {
std::unordered_map<std::string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 88;
scores["Cathy"] = 91;

for (std::unordered_map<std::string, int>::iterator it = scores.begin(); it != scores.end(); ++it) {
std::cout << it->first << " = " << it->second << '\n';
}

return 0;
}

输出顺序可能和插入顺序不同,也不保证每次运行都一样。

常用成员函数

函数作用
begin() / end()遍历容器
empty()是否为空
size()元素数量
clear()清空
insert()插入键值对
erase()删除键或迭代器位置元素
find()查找键
count()统计键出现次数(0 或 1)
at()按键访问,不存在会抛异常
bucket_count()当前桶数量
load_factor()当前负载因子
rehash(n)调整桶数量
reserve(n)预留至少可容纳 n 个元素的桶容量

插入与更新

#include <string>
#include <unordered_map>

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

m.insert(std::make_pair(std::string("apple"), 3));
m["banana"] = 5;

std::pair<std::unordered_map<std::string, int>::iterator, bool> result =
m.insert(std::make_pair(std::string("apple"), 100));

if (!result.second) {
result.first->second = 100;
}

return 0;
}

insert 在键已存在时不会覆盖旧值;operator[] 会覆盖。

查找与删除

#include <iostream>
#include <string>
#include <unordered_map>

int main() {
std::unordered_map<std::string, int> m;
m["cpp"] = 11;
m["c"] = 98;

std::unordered_map<std::string, int>::iterator it = m.find("cpp");
if (it != m.end()) {
std::cout << it->first << " = " << it->second << '\n';
}

m.erase("c");
return 0;
}

如果只想查询是否存在,优先 findcount,避免误用 operator[] 导致意外插入。

哈希与桶

unordered_map 把元素按哈希值分配到不同桶中。当元素太多、负载因子过高时,可能触发 rehash,重新分配桶并重排元素。

#include <iostream>
#include <unordered_map>

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

m.reserve(1000);
for (int i = 0; i < 1000; ++i) {
m[i] = i * i;
}

std::cout << "bucket_count = " << m.bucket_count() << '\n';
std::cout << "load_factor = " << m.load_factor() << '\n';

return 0;
}

在已知数据规模时提前 reserve,可减少 rehash 次数并提升整体性能。

自定义键类型

当键是自定义类型时,需要提供哈希函数和相等比较。

#include <iostream>
#include <string>
#include <unordered_map>

struct User {
std::string name;
int id;
};

struct UserHash {
std::size_t operator()(const User& u) const {
return std::hash<std::string>()(u.name) ^ (std::hash<int>()(u.id) << 1);
}
};

struct UserEqual {
bool operator()(const User& lhs, const User& rhs) const {
return lhs.name == rhs.name && lhs.id == rhs.id;
}
};

int main() {
std::unordered_map<User, int, UserHash, UserEqual> scores;

User tom;
tom.name = "Tom";
tom.id = 1;

scores[tom] = 95;
std::cout << scores[tom] << '\n';
return 0;
}

哈希函数和相等比较应保持一致性:若两个键相等,它们的哈希值也应相同。

unordered_map 与 map 如何选择

  1. 只关心快速查找,不关心顺序:优先 unordered_map
  2. 需要按键有序遍历、范围查询:选择 map
  3. 数据量较大、键分布较好时:unordered_map 通常更快。
  4. 需要稳定有序输出结果:map 更适合。

使用注意

  1. 不要依赖 unordered_map 的遍历顺序。
  2. operator[] 可能导致隐式插入。
  3. rehash 后,迭代器可能失效。
  4. 最坏情况下复杂度可能退化到 O(n)
  5. 自定义键时,务必正确实现哈希与相等比较。

小结

std::unordered_map 是 C++11 里非常高频的容器之一,适合“键唯一 + 高频查找”的场景。掌握 insertfinderasereserve 以及自定义哈希后,就能覆盖大多数工程中的映射需求。