cpp20规范 vs2019 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的数据结构,随后整理电脑版。
在这里插入图片描述
谢谢。至此,解决了源码阅读中的一个疑问。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值