文章目录
1. unordered_map底层实现原理
一句话概括:unordered_map 是基于散列表实现的 map。
1.1 散列表
-
定义和操作
散列表是一种数据结构,它通过哈希函数把键(key)映射到哈希表的一个位置,然后在这个位置上存储键值对(key-value pair)。在 C++ 中,哈希表可以通过一个数组和一个哈希函数实现。
template<typename Key, typename Value> class HashTable { vector<list<pair<Key, Value>>> data; // 哈希表数据 // 其他数据成员和函数成员... };
-
哈希碰撞和解决方案
哈希碰撞是指多个不同的键被哈希函数映射到哈希表的同一个位置。主要有以下三种处理哈希碰撞的方法:
- 线性探测:当碰撞发生时,向后寻找下一个空位存放数据。
- 开放寻址:同样是在发生碰撞时向后寻找,但会使用二次哈希等方法改变探测的步长。
- 拉链法:使用链表处理碰撞,把同一位置的键值对存入一个链表。
STL 中的 unordered_map 使用的是拉链法处理哈希碰撞。
// 拉链法的实现示例 int hashFunc(Key key) { /* 哈希函数... */ } void insert(Key key, Value value) { int index = hashFunc(key); data[index].push_back(make_pair(key, value)); // 在链表尾部添加键值对 }
-
负载因子
负载因子是散列表的实际元素数量和位置数量(桶的数量)的比值。当负载因子过高时,哈希碰撞的概率增加,查询效率降低。
-
重新哈希
当负载因子大于预设阈值(通常为1)时,会发起重新哈希(rehash),即扩大位置数量,并重新分配所有元素。
void rehash() { // 创建新的更大的哈希表,把所有元素重新分配到新的哈希表 }
1.2 STL 中的 unordered_map 的实现
-
哈希表的模板参数
unordered_map
采用了复杂的模板参数设计,主要包括键类型_Key
、值类型_Value
、内存分配器_Alloc
、提取键的函数_ExtractKey
、比较键是否相等的函数_Equal
、哈希函数_H1
、映射函数_H2
、调用哈希函数和映射函数的函数_Hash
、重新哈希策略_RehashPolicy
以及内存相关的_Traits
等。 -
数据成员
_M_buckets
:哈希表的指针数组,数组的每个元素是一个链表。_M_bucket_count
:数组长度,即哈希表的位置数量。_M_element_count
:实际存储的元素数量。_M_before_begin
:一个特殊的节点,它的下一个节点是哈希表的第一个节点。_M_rehash_policy
:重新哈希的策略对象,它决定何时需要重新哈希。_M_single_bucket
:一个临时的单节点桶,用于临时存储元素。
-
节点定义
每个节点是一个结构体,包含了键值对的存储空间
_M_storage
和其他需要的信息。struct _Hash_node : _Hash_node_base { __stored_ptr _M_storage; };
-
主要的接口
unordered_map
主要的接口包括插入、查找、删除等操作,它们的实现都是调用哈希表的对应函数。其中,插入操作_M_insert_bucket_begin
是先确定插入的位置,然后在这个位置的链表头部插入新的键值对。
1.3 unordered_map
-
定义
unordered_map
是一个模板类,它的模板参数包括键类型、值类型、哈希函数、比较函数和内存分配器。它的主要数据成员是一个_Hashtable
对象。template<typename _Key, typename _Tp, typename _Hash = std::hash<_Key>, typename _Pred = std::equal_to<_Key>, typename _Alloc = std::allocator<std::pair<