c++ unordered_map底层实现
C++ unordered_map 底层实现
unordered_map 底层是哈希表,采用链地址法解决哈希冲突,C++标准只规定接口和复杂度,不强制底层细节,主流实现(GCC libstdc++):桶数组 + 单向链表。
头文件:
#include <unordered_map>存储单元:pair<const Key, T>,key 不可修改
1. 整体结构
bucket array(桶数组,vector实现)
├─ bucket0 → 节点1 → 节点2 → nullptr
├─ bucket1 → 节点3 → nullptr
├─ bucket2 → nullptr
└─ bucket3 → 节点4 → 节点5 → nullptr
- 桶(bucket):桶数组里的一个位置,挂一条单向链表。
- 节点:存放
pair<const Key, T>,以及链表下一个节点指针。
插入流程
- 调用哈希函数
hash<Key>(key),得到哈希值; - 哈希值对桶数量取模,得到桶下标;
- 遍历该桶链表,用
==判断key是否已存在:- 存在:直接更新value;
- 不存在:新建节点,插到链表头部(GCC)。
查找流程
- 计算key哈希值,定位桶;
- 遍历桶内链表,逐个比较key是否相等;
- 找到返回迭代器,找不到返回
end()。
重点:哈希只用来定位桶,判定key相等必须用 ==。哈希相同 ≠ key相等(哈希冲突)。
2. 模板参数
template<
class Key,
class T,
class Hash = std::hash<Key>, // 哈希函数对象
class KeyEqual = std::equal_to<Key>,// key相等判断
class Allocator = std::allocator<std::pair<const Key, T>>
> class unordered_map;
规则:如果两个key相等,则它们的哈希值必须相等,否则哈希表逻辑错误。
内置类型如 int、std::string 标准库已经提供 std::hash;自定义结构体当key,需要自己提供hash函数和==运算符。
3. 负载因子 & rehash(重哈希)
$$load_factor = \frac{元素总数}{桶的数量}$$
max_load_factor():最大负载因子,默认1.0- 当
load_factor > max_load_factor,触发 rehash
rehash做的事情
- 开辟一块更大的桶数组(GCC一般选质数个数的桶,减少冲突);
- 遍历旧哈希表所有节点,重新计算哈希,迁移到新桶;
- 释放旧桶数组内存。
⚠️ rehash 会全部迭代器失效;但元素本身的指针/引用依然有效,节点只是换桶。
reserve(n):预分配桶,预留空间,避免中途多次rehash。
4. 重要特性(高频面试考点)
- 无序:遍历顺序不是key大小顺序,也不是插入顺序,每次rehash后遍历顺序会变。
- 时间复杂度:平均 O(1),最坏(大量冲突,链表很长)O(n)。
- GCC libstdc++ 的
unordered_map:桶内永远是单向链表,不会转红黑树!这点和 Java HashMap 不一样(Java 链表长度超过阈值转为红黑树),C++标准没有这个要求。
5. unordered_map vs map
| unordered_map | map | |
|---|---|---|
| 底层 | 哈希表(链地址) | 红黑树 |
| 有序 | 无序 | key升序有序 |
| 复杂度 | 平均O(1),最坏O(n) | O(logn) |
| 迭代器失效 | rehash全部失效;删除仅被删迭代器失效 | 插入几乎不失效;删除仅被删迭代器失效 |
| 自定义key | 需要hash函数 + == | 需要 < 比较运算符 |
6. 常见坑
- key是
const,不能修改key,修改会破坏哈希表; - 自定义key忘记提供hash函数,编译报错;
- 频繁插入导致多次rehash,性能抖动,可以提前
reserve; - 不要依赖遍历顺序。
7. 简易模拟实现(极简版)
#include <vector>
#include <list>
#include <functional>
#include <utility>
template<typename K, typename V>
class MyHashmap {
private:
std::vector<std::list<std::pair<K,V>>> buckets;
size_t bucket_num;
size_t elem_cnt;
public:
MyHashmap(size_t n = 10) : bucket_num(n), elem_cnt(0), buckets(n) {}
size_t get_bucket(const K& key) {
return std::hash<K>{}(key) % bucket_num;
}
void insert(const K& key, const V& val) {
size_t idx = get_bucket(key);
auto& bucket = buckets[idx];
for(auto& p : bucket) {
if(p.first == key) {
p.second = val;
return;
}
}
bucket.emplace_back(key, val);
elem_cnt++;
// 负载因子超过阈值,rehash
if((double)elem_cnt / bucket_num > 1.0) {
rehash();
}
}
void rehash() {
auto old_buckets = std::move(buckets);
bucket_num *= 2;
buckets.resize(bucket_num);
elem_cnt = 0;
for(auto& list : old_buckets) {
for(auto& p : list) {
insert(p.first, p.second);
}
}
}
};
参考资料:
浙公网安备 33010602011771号