cpp20规范 vs2019 STL库 unordered_map源码分析

文章详细解析了C++STL中unordered_map的内部实现,涉及哈希表的数据结构、哈希函数的效率与冲突处理策略,以及透明容器的概念。作者通过实验和源码分析解答了相关疑问。

(0) 这个序号0 是最后写的,阅读、注释完以后的总结版。所以放在最前面了。后面的序号,记录的是阅读源码过程中的一些疑问和分析过程。也都保留下来了。
哈希表。数组的查询比二叉树要快。哈希表的实质就是数组。
接着,手绘一个哈希表的数据结构,以后,网上会有更好的。这会咱们先凑合看
在这里插入图片描述
STL哈希表的实现,并不是把键值对直接存储在数组里。而是把键值对的地址,存储在哈希数组里,当然在数组里的存储位置,是依据哈希函数计算出来的。这样一次访问内存,即可获得数据。所以数组的访问效率比二叉树的指针查找要快些。很多键值对是作为双向链表的节点,连接在一起的。
所以,哈希表实际是封装了两个大的数据结构,一个是list双向链表,一个是vector可扩容数组。
(1) unordered_map 模板有如下的模板调用和包含关系。
在这里插入图片描述
绿箭头1 处的模板类,继承了绿箭头2 , 绿箭头2 又继承了红框模板。但红框模板有一个泛化版本和一个特化版本,选择哪一个呢?
经过源代码查找,_Hasher = hash<_Kty>,和 _Keyeq = equal_to<_Kty>里都没有 is_transparent 的定义。而哈希时int 整数应该是最常用的情况。所以应该是透明容器。以下做了运行验证。先修改STL源文件,如下图,添加两个验证函数
在这里插入图片描述
再进行打印输出:
在这里插入图片描述
所以应该是继承了泛化版本,就是透明的哈希容器。虽然,还不知道透明是啥意思。只是这个模板里强制了一个类型引用。
这个结论先这么认为。把源码改回去。继续,随后补充。

(2)vs2019里,哈希函数的效率如何,能避免哈希冲突么?
这个哈希函数的算法很复杂。不太容易明白,是数学问题。我们先看看这个哈希函数的使用效果。
在这里插入图片描述
以上的图里,给出了vs2019哈希函数的源码。我们测试一下这个函数的运行效果:
在这里插入图片描述
如上图,往16个元素的数组里存储15个数,竟然没有发生哈希冲突。这个函数属实优秀,完美。所以,我们可以放心大胆的哈希了。
再附上一张哈希函数的实验图。
在这里插入图片描述
为什么要单独测试这里哈希函数的效率和计算结果呢?因为如果出现哈希冲突,按我的理解,STL库源码的设计是,把后待插入的list节点,插入到已在哈希表里的list节点的前面。就是说后到来的数据节点,反而离list的头部更近。无序map里维护了一个list链表,把无序map里的所有数据节点串起来。list是哈希表扩容时候,要访问的数据来源。但从实验可见,找到这样一个哈希冲突太难了。只能仔细阅读分析源码,凭借c++语法和代码逻辑来体会STL库编写大师的想法了。谢谢

(3)条目2是下午写的。出去散步时想到,如何产生哈希冲突。把大量数据映射到很小的数组里,必然会产生哈希冲突。
如下图这样的实验:
在这里插入图片描述
从中挑选出了5个会产生哈希冲突的值来插在8个槽位的哈希容器里。如下图:
在这里插入图片描述
我们编写如下代码,生成无序map:
在这里插入图片描述
从上图,还得知了无序map在64位编程环境下的大小是80个字节,这个数值计算,编译器考虑了内存对齐。
打断点,跟踪变量 u 的内存,把所有内存数据拿到后,证明我们的这个结论是正确的,这也说明咱们对源代码的理解是正确的。这样是省事的做法,避免了汇编指令级别的跟踪:因为如果出现哈希冲突,STL库源码的设计是,把后待插入的冲突list节点,插入到已在哈希表里的list节点的前面。就是说后到来的哈希冲突的数据节点,反而离list的头部更近
以下的图是简单的手算的哈希表的元素节点插入过程:
在这里插入图片描述
经内存上的数据的地址的验证,咱们的猜测是正确的。并附上无序map的数据结构,随后整理电脑版。
在这里插入图片描述
谢谢。至此,解决了源码阅读中的一个疑问。

参考的资料中提及了unordered_map成员函数,但未给出详细解释。下面为你提供unordered_map一些常见成员函数的详细解释: ### 元素访问 - `at(key)`:返回键为`key`的元素的引用。如果`key`不存在,会抛出`std::out_of_range`异常。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}, {2, 20}}; try { std::cout << myMap.at(1) << std::endl; // 输出 10 } catch (const std::out_of_range& e) { std::cout << e.what() << std::endl; } return 0; } ``` - `operator[]`:通过键`key`访问元素。如果`key`不存在,会插入一个默认构造的元素。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap; myMap[1] = 10; std::cout << myMap[1] << std::endl; // 输出 10 return 0; } ``` ### 元素修改 - `insert()`:插入元素或元素范围。有多种重载形式,可插入单个元素、初始化列表等。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap; myMap.insert({1, 10}); std::pair<int, int> p(2, 20); myMap.insert(p); for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` - `emplace()`:原位构造元素。与`insert`不同,它直接在容器中构造元素,而不是先创建对象再插入。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap; myMap.emplace(1, 10); for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` - `emplace_hint()`:带提示的原位构造元素。提示迭代器位置,可提高插入效率。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}}; auto it = myMap.begin(); myMap.emplace_hint(it, 2, 20); for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` - `erase()`:删除指定位置、指定键或指定范围的元素。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}, {2, 20}}; myMap.erase(1); // 删除键为 1 的元素 for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; } ``` ### 容量相关 - `empty()`:检查容器是否为空。如果容器为空,返回`true`,否则返回`false`。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap; std::cout << (myMap.empty() ? "Empty" : "Not empty") << std::endl; return 0; } ``` - `size()`:返回容器中元素的数量。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}, {2, 20}}; std::cout << myMap.size() << std::endl; // 输出 2 return 0; } ``` ### 迭代器相关 - `begin()`:返回指向容器首元素的迭代器。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}, {2, 20}}; auto it = myMap.begin(); if (it != myMap.end()) { std::cout << it->first << ": " << it->second << std::endl; } return 0; } ``` - `end()`:返回指向容器尾后位置的迭代器。 ### 查找相关 - `find(key)`:查找键为`key`的元素。如果找到,返回指向该元素的迭代器;否则返回`end()`。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}, {2, 20}}; auto it = myMap.find(1); if (it != myMap.end()) { std::cout << it->first << ": " << it->second << std::endl; } return 0; } ``` - `count(key)`:返回键为`key`的元素的数量。对于`unordered_map`,结果只能是 0 或 1。示例代码如下: ```cpp #include <iostream> #include <unordered_map> int main() { std::unordered_map<int, int> myMap = {{1, 10}, {2, 20}}; std::cout << myMap.count(1) << std::endl; // 输出 1 return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangzhangkeji

谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值