概述
std::unordered_map是C++标准模板库(STL)中的一个关联容器,实现基于哈希表的键值对映射。自C++11起成为标准库的一部分,位于 <unordered_map>头文件中。
核心特性
数据结构
-
基于哈希表:使用散列函数将键映射到存储桶(bucket)
-
无序存储:元素不以特定顺序存储,遍历顺序不确定
-
唯一键:每个键在容器中只能出现一次
-
平均常数时间复杂度:插入、删除、查找操作平均O(1),最坏情况O(n)
模板声明
template<
class Key,
class T,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>
> class unordered_map;
基本操作
1. 构造与初始化
// 默认构造
std::unordered_map<std::string, int> map1;
// 初始化列表构造
std::unordered_map<std::string, int> map2 = {
{"apple", 1},
{"banana", 2},
{"orange", 3}
};
// 范围构造
std::vector<std::pair<std::string, int>> vec = {{"a", 1}, {"b", 2}};
std::unordered_map<std::string, int> map3(vec.begin(), vec.end());
// 拷贝构造
std::unordered_map<std::string, int> map4(map2);
2. 元素访问
// operator[] - 访问或插入(键不存在时插入默认值)
int value = map["apple"]; // 如果不存在,会插入{"apple", 0}
map["apple"] = 10; // 修改或插入
// at() - 边界检查访问
try {
int val = map.at("apple"); // 存在则返回
} catch (const std::out_of_range& e) {
// 键不存在时抛出异常
}
// find() - 安全查找
auto it = map.find("apple");
if (it != map.end()) {
// 找到元素,it->first是键,it->second是值
}
3. 修改操作
// 插入元素
auto result = map.insert({"apple", 1}); // 返回pair<iterator, bool>
map.emplace("banana", 2); // 原地构造,更高效
// 插入或赋值 (C++17)
map.insert_or_assign("apple", 3); // 存在则赋值,不存在则插入
// 删除元素
map.erase("apple"); // 按键删除
auto it = map.find("banana");
if (it != map.end()) {
map.erase(it); // 按迭代器删除
}
map.erase(map.begin(), map.end()); // 范围删除
// 清空
map.clear();
4. 容量查询
bool empty = map.empty(); // 是否为空
size_t size = map.size(); // 元素数量
size_t max = map.max_size(); // 理论最大容量
哈希表特定操作
1. 桶接口
// 桶数量相关
size_t bucket_count = map.bucket_count(); // 桶总数
size_t bucket_idx = map.bucket("apple"); // 键所在的桶索引
// 桶信息
size_t bucket_size = map.bucket_size(0); // 指定桶中的元素数
// 负载因子
float load_factor = map.load_factor(); // 平均每个桶的元素数
float max_lf = map.max_load_factor(); // 最大负载因子
map.max_load_factor(0.75f); // 设置最大负载因子
2. 哈希策略
// 调整桶数量
map.rehash(100); // 确保至少有100个桶
map.reserve(1000); // 确保能容纳1000个元素而不超过最大负载因子
迭代器
类型
// 迭代器类型
std::unordered_map<std::string, int>::iterator it;
std::unordered_map<std::string, int>::const_iterator cit;
std::unordered_map<std::string, int>::local_iterator lit; // 桶内迭代器
遍历方式
// 1. 基于范围的for循环 (C++11)
for (const auto& [key, value] : map) { // C++17结构化绑定
// 使用key和value
}
// 2. 迭代器遍历
for (auto it = map.begin(); it != map.end(); ++it) {
const auto& key = it->first;
const auto& value = it->second;
}
// 3. 遍历所有桶
for (size_t i = 0; i < map.bucket_count(); ++i) {
for (auto lit = map.begin(i); lit != map.end(i); ++lit) {
// 遍历第i个桶
}
}
自定义类型作为键
1. 定义哈希函数
struct Person {
std::string name;
int age;
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 自定义哈希函数
struct PersonHash {
size_t operator()(const Person& p) const {
return std::hash<std::string>()(p.name) ^
(std::hash<int>()(p.age) << 1);
}
};
// 使用自定义哈希
std::unordered_map<Person, std::string, PersonHash> person_map;
2. 使用标准库组合哈希 (C++14)
#include <functional>
struct PersonHash {
size_t operator()(const Person& p) const {
return std::hash<std::string>{}(p.name) ^
(std::hash<int>{}(p.age) << 1);
}
};
性能特性
时间复杂度
| 操作 | 平均情况 | 最坏情况 |
|---|---|---|
| 插入 | O(1) | O(n) |
| 删除 | O(1) | O(n) |
| 查找 | O(1) | O(n) |
| 遍历 | O(n) | O(n) |
空间复杂度
-
需要额外的空间存储桶数组和链表指针
-
通常比
std::map占用更多内存
与std::map对比
| 特性 | std::unordered_map |
std::map |
|---|---|---|
| 底层结构 | 哈希表 | 红黑树 |
| 顺序 | 无序 | 按键排序 |
| 操作复杂度 | 平均O(1) | O(log n) |
| 内存使用 | 通常更高 | 通常更低 |
| 迭代器稳定性 | 插入可能使所有迭代器失效 | 除删除元素外迭代器稳定 |
| 自定义键要求 | 需要哈希函数 | 需要比较函数 |
最佳实践
1. 预分配空间
std::unordered_map<int, std::string> map;
map.reserve(1000); // 预分配,避免多次rehash
2. 选择合适的哈希函数
-
对于整数类型,标准哈希通常足够
-
对于字符串,考虑使用更好的分布
-
避免哈希冲突过多
3. 查找优化
// 避免重复查找
auto it = map.find(key);
if (it != map.end()) {
// 使用it->second
} else {
// 键不存在
}
// 而不是:
if (map.count(key) > 0) { // 额外查找
int value = map[key]; // 再次查找
}
4. 遍历时修改
// 安全删除遍历中的元素
for (auto it = map.begin(); it != map.end(); ) {
if (condition) {
it = map.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
线程安全性
-
多个线程可以同时读取容器
-
如果有任何线程修改容器,所有线程访问都需要同步
-
使用
std::shared_mutex(C++17)或互斥锁保护并发访问
特殊成员函数 (C++17)
1. extract()- 节点句柄
// 提取节点而不重新分配内存
auto nh = map.extract("apple");
if (!nh.empty()) {
nh.key() = "new_apple"; // 修改键
map.insert(std::move(nh)); // 重新插入
}
2. merge()- 合并容器
std::unordered_map<std::string, int> map1, map2;
// ... 填充数据
map1.merge(map2); // 尝试合并所有元素
常见问题与解决方案
1. 哈希冲突过多
-
调整负载因子
-
使用更好的哈希函数
-
增加桶数量
2. 自定义键的哈希质量差
-
确保哈希函数分布均匀
-
考虑使用现成的哈希组合库
3. 迭代器失效
-
插入操作可能使所有迭代器失效
-
删除操作只使指向被删元素的迭代器失效
总结
std::unordered_map提供了高效的键值对存储,适合需要快速查找而不关心顺序的场景。正确使用时,其性能通常优于基于树的std::map。使用时需注意内存使用、哈希函数质量和线程安全性。