字典哈希表的实现原理_GCC中unordered_(multi)set/map的实现原理 (Part 2 图解哈希表结构)...

8a55759b746f584610a91c30b0092d67.png

写在前面

(本专栏仅是个人笔记本,有胡言乱语和错漏部分)

本文以图文+代码的形式记录了_Hashtable的结构,如何编排每一个bucket的链表,如何将每个bucket的链表串在一起形成一个长链表,如何利用迭代器遍历_Hashtable。

此外,还解释了insert操作的流程,以及为什么cache hash值可以提高_Hashtable的效率。

_Hashtable的数据成员

  312     private:313       __bucket_type*            _M_buckets              = &_M_single_bucket;314       size_type                 _M_bucket_count         = 1;315       __node_base               _M_before_begin;316       size_type                 _M_element_count        = 0;317       _RehashPolicy             _M_rehash_policy;
  • _Bucket[] _M_buckets 数组,数组元素类型为Node*
  • _Hash_node_base _M_before_begin 容器的哨兵节点
  • size_type _M_bucket_countbucket的数量,若采用Prime_rehash,则这个数一直是个素数
  • size_type _M_element_count 容器中元素的数量

Node

268  template<typename _Value, bool _Cache_hash_code>269  struct _Hash_node;

有如下的数据成员:

  • _Hash_node_base* _M_nxt;
  • __gnu_cxx::__aligned_buffer<_Value> _M_storage; 为什么不直接用_Value v呢?
  • (可选,通过特例化添加)std::size_t _M_hash_code; 当cache时,存在这个数据成员。

迭代器

对于(multi)set,返回的迭代器只能是只读的:

341       typedef std::forward_iterator_tag                 iterator_category;342 343       using pointer = typename std::conditional<__constant_iterators,344                                                 const _Value*, _Value*>::type; //根据模板参数__constant_iterators决定是否const345 346       using reference = typename std::conditional<__constant_iterators,347                                                   const _Value&, _Value&>::type;  //根据模板参数__constant_iterators决定是否const

其他部分,跟forward_list的迭代器大同小异。

_ExtractKey

STL预设了两种从_Value中提取key的方法:

88   struct _Identity //for set89   {90     template<typename _Tp>91       _Tp&&92       operator()(_Tp&& __x) const93       { return std::forward<_Tp>(__x); }94   };95 96   struct _Select1st  // for map97   {98     template<typename _Tp>99       auto100       operator()(_Tp&& __x) const101       -> decltype(std::get<0>(std::forward<_Tp>(__x)))102       { return std::get<0>(std::forward<_Tp>(__x)); }103   };104

_Hashtable的结构

_Hashtable的可以看作一个vector ,每一个bucket存储的是一个Node* 类型的指针,并具有如下特点:

  • 当该bucket为空时,该bucket指针为null
  • 每个哈希容器有一个哨兵节点(_M_before_begin),作为使用迭代器遍历容器的起点。
  • 每一个bucket链表的最后一个结点,同时又是某一个bucket的哨兵节点。

用图描绘一下就是:

c13bbaa7549038858fe1f0936692242a.png

d11f75173daeb51b0c13ea7a3d032880.png

740c1d57c7d580ac9f537e20abeba3fa.png

insert

insert定义在_Insert_base中,4种哈希容器公用一个insert函数:

710       __ireturn_type  //ireturn_type对每个哈希容器不同711       insert(const value_type& __v)712       {713         __hashtable& __h = _M_conjure_hashtable();714         __node_gen_type __node_gen(__h);715         return __h._M_insert(__v, __node_gen, __unique_keys()); // false_type or true_type716       }

然后根据 __unique_keys(),调用不同重载版本的_M_insert():

//for multiset/multimap687       template<typename _Arg, typename _NodeGenerator>688         iterator689         _M_insert(_Arg&& __arg, const _NodeGenerator& __node_gen,690                   std::false_type __uk)691         {692           return _M_insert(cend(), std::forward<_Arg>(__arg), __node_gen,693                            __uk);694         }//for map/set1683   // Insert v if no element with its key is already present.1684   template<typename _Key, typename _Value,1685            typename _Alloc, typename _ExtractKey, typename _Equal,1686            typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,1687            typename _Traits>1688     template<typename _Arg, typename _NodeGenerator>1689       auto1690       _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,1691                  _H1, _H2, _Hash, _RehashPolicy, _Traits>::1692       _M_insert(_Arg&& __v, const _NodeGenerator& __node_gen, std::true_type)1693       -> pair<iterator, bool>1694       {1695         const key_type& __k = this->_M_extract()(__v);1696         __hash_code __code = this->_M_hash_code(__k);1697         size_type __bkt = _M_bucket_index(__k, __code);1698 1699         __node_type* __n = _M_find_node(__bkt, __k, __code);1700         if (__n)1701           return std::make_pair(iterator(__n), false);1702 1703         __n = __node_gen(std::forward<_Arg>(__v));1704         return std::make_pair(_M_insert_unique_node(__bkt, __code, __n), true);1705       }

这里我们暂时只关注unordered_map/set的插入。

在插入之前,首先在bucket中查找,先用 _M_find_node判断是否存在key相同的元素, _M_find_node又是基于_M_find_before_node的,其代码如下。

1413   // Find the node whose key compares equal to k in the bucket n.1414   // Return nullptr if no node is found.1415   template<typename _Key, typename _Value,1416            typename _Alloc, typename _ExtractKey, typename _Equal,1417            typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,1418            typename _Traits>1419     auto1420     _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,1421                _H1, _H2, _Hash, _RehashPolicy, _Traits>::1422     _M_find_before_node(size_type __n, const key_type& __k,1423                         __hash_code __code) const1424     -> __node_base*1425     {1426       __node_base* __prev_p = _M_buckets[__n];1427       if (!__prev_p)1428         return nullptr;1429 1430       for (__node_type* __p = static_cast<__node_type*>(__prev_p->_M_nxt);;1431            __p = __p->_M_next())1432         {1433           if (this->_M_equals(__k, __code, __p))1434             return __prev_p;1435        //由于_Hashtable的结构特殊,每次都要判断是否已经遍历完了当前的bucket1436           if (!__p->_M_nxt || _M_bucket_index(__p->_M_next()) != __n)1437             break;1438           __prev_p = __p;1439         }1440       return nullptr;1441     }

在1436行会频繁调用_M_bucket_index(Node *)这个函数,如果cache了hash值,_M_bucket_index仅需一次mod操作,否则就是extract_key, hash, mod这三个操作都要进行。这就是cache hash值的意义。GCC注释中也是这么解释的:

Walking through a bucket's nodes requires a check on the hash code to see if each node is still in the bucket. Such a design assumes a quite efficient hash functor and is one of the reasons it is highly advisable to set __cache_hash_code to true.

如果不存在相同key的元素,_M_insert进而调用了_M_insert_unique_node执行插入:

_M_insert_unique_node(size_type __bkt, __hash_code __code, __node_type* __node)

函数功能:已经确定表中没有相同key的元素,向表中插入新的元素。

//无特例化1581   template<typename _Key, typename _Value,1582            typename _Alloc, typename _ExtractKey, typename _Equal,1583            typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,1584            typename _Traits>1585     auto1586     _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,1587                _H1, _H2, _Hash, _RehashPolicy, _Traits>::1588     _M_insert_unique_node(size_type __bkt, __hash_code __code,1589                           __node_type* __node)1590     -> iterator1591     {//交由_RehashPolicy 判断 是否需要rehash,1592       const __rehash_state& __saved_state = _M_rehash_policy._M_state();1593       std::pair<bool, std::size_t> __do_rehash1594         = _M_rehash_policy._M_need_rehash(_M_bucket_count, _M_element_count, 1); //1 表示要插入一个元素1595 1596       __try1597         {1598           if (__do_rehash.first) //需要rehash1599             {1600               _M_rehash(__do_rehash.second, __saved_state);1601               __bkt = _M_bucket_index(this->_M_extract()(__node->_M_v()), __code);1602             }1603 1604           this->_M_store_code(__node, __code); //在node中保存哈希值1605 1606           // Always insert at the beginning of the bucket.1607           _M_insert_bucket_begin(__bkt, __node);1608           ++_M_element_count; //哈希表中的总元素数目1609           return iterator(__node);1610         }1611       __catch(...)1612         {1613           this->_M_deallocate_node(__node);1614           __throw_exception_again;1615         }1616     }

_M_insert_unique_node又调用了_M_insert_bucket_begin

_M_insert_bucket_begin(size_type __bkt, __node_type* __node)

函数作用:在bucket处的链表头部插入新节点,若这是该bucket的第一个节点,则会有额外的指针操作。 结合注释并参考上面的图,还是挺好理解的

1443   template<typename _Key, typename _Value,1444            typename _Alloc, typename _ExtractKey, typename _Equal,1445            typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,1446            typename _Traits>1447     void1448     _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,1449                _H1, _H2, _Hash, _RehashPolicy, _Traits>::1450     _M_insert_bucket_begin(size_type __bkt, __node_type* __node)1451     {1452       if (_M_buckets[__bkt])1453         {1454           // Bucket is not empty, we just need to insert the new node1455           // after the bucket before begin.1456           __node->_M_nxt = _M_buckets[__bkt]->_M_nxt;1457           _M_buckets[__bkt]->_M_nxt = __node;1458         }1459       else1460         {1461           // The bucket is empty, the new node is inserted at the1462           // beginning of the singly-linked list and the bucket will1463           // contain _M_before_begin pointer.1464           __node->_M_nxt = _M_before_begin._M_nxt;1465           _M_before_begin._M_nxt = __node;1466           if (__node->_M_nxt)1467             // We must update former begin bucket that is pointing to1468             // _M_before_begin.1469             _M_buckets[_M_bucket_index(__node->_M_next())] = __node;1470           _M_buckets[__bkt] = &_M_before_begin;1471         }1472     }

至此,插入完毕。

begin end size

结合图例,很容易可以给出begin, end和size的实现:

368  __node_type*369  _M_begin() const370  { return static_cast<__node_type*>(_M_before_begin._M_nxt); } //返回哨兵节点的next指针481  iterator482  begin() noexcept483  { return iterator(_M_begin()); }489  iterator490  end() noexcept491  { return iterator(nullptr); }505  size_type506  size() const noexcept507  { return _M_element_count; }

其他成员函数

只要理解了Hashtable的结构,其他成员函数的代码还是比较self-explained的,这里就暂且略过。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/561205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

null === undefined_【英】两个“非值”:undefined 和 null

前言本期英文由Dr. Axel Rauschmayer分享。英文从这开始&#xff5e;&#xff5e;Most programming languages have only one value for “no value” or “empty reference”. For example, that value is null in Java. JavaScript has two of those special values: undefin…

有趣的编程代码_iPad amp; Mac 编程游戏推荐

今天想给大家推荐一款免费的编程 App&#xff0c;它的名字叫「Swift Playgrounds」。会不会有人一看到“编程”两个字就觉得怕了怕了&#xff0c;感觉太深奥了&#xff1f;但是&#xff0c;这款游戏真的简单又有趣&#xff0c;值得你去试试。▍应用介绍这是一款适用于 Mac 和 i…

tensorflow支持python3.7吗_TensorFlow2.1正式版上线:最后一次支持Python2,进一步支持TPU...

机器之心报道参与&#xff1a;杜伟、一鸣TensorFlow2.1的更新&#xff0c;能够让弃坑的用户回心转意吗&#xff1f;去年 10 月&#xff0c;谷歌才发布了 TensorFlow 2.0 正式版。时隔三个月后&#xff0c;昨日官方发布了 TensorFlow 2.1&#xff0c;本次版本更新带了了多项新特…

hashmap containsvalue时间复杂度_不看看HashMap源码,怎么和面试官谈薪资

HashMap 是日常开发中&#xff0c;用的最多的集合类之一&#xff0c;也是面试中经常被问到的 Java 类之一。同时&#xff0c;HashMap 在实现方式上面又有十分典型的范例。不管是从哪一方面来看&#xff0c;学习 HashMap 都可以说是有利无害的。分析 HashMap 的源码的文章在网上…

tcp序列号为什么是随机的_译文:每个开发人员应了解的 TCP 知识

为什么要把服务器放在离用户很近的地理位置上&#xff1f;其中一个原因是为了实现更低的延迟。当您发送的数据是短的、应该尽可能快的传输数据时&#xff0c;这很有意义。但如果是大文件&#xff0c;比如视频等大文件呢&#xff1f;当然&#xff0c;在接收第一个字节时肯定会有…

matlab时域转换成频域_从时域到频域,你只需要旋转一下!

作为一个工科生&#xff0c;这里我不去说很多的严密的定理和知识&#xff0c;我只是从我的角度&#xff0c;形象的去理解时域和频域。首先我们来观察一个画在空间直角坐标系中的正弦函数&#xff1a;现在我们从两个视角去观察它分别是垂直于xoz面和垂直于yoz面看到的图像如下&a…

win7右键计算机管理参数错误,win7纯净版虚拟磁盘管理器参数错误怎么解决?

最近有用户反映win7纯净版虚拟磁盘管理器参数错误&#xff0c;他是硬盘分区太多&#xff0c;很乱&#xff0c;所以想要自己设置盘符名称&#xff0c;但没想到在修改盘符时出现提示“硬盘参数错误”&#xff0c;导致修改盘符失败&#xff0c;这让用户非常苦恼。那么&#xff0c;…

mysql查看用户名_Mysql创建数据表的方法介绍(附示例)

本篇文章给大家带来的内容是关于Mysql创建数据表的方法介绍(附示例)&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。数据表是数据库最重要的组成部分之一&#xff0c;是其他对象的基础。如果我们的数据库没有数据表就等于没有…

vue读取终端硬件信息_双通道RFID模块助力电力数据采集终端(不必多说,直接测试对比)...

推动超高频技术应用的RFID模块打包模组、全面简化设计极高的性价比&#xff0c;让更多领域用得起超高频RFIDM6002 是我司自主研发的一款高性能双通道嵌入式超高频RFID 读写模块&#xff0c;该模块主要是解决单通道读写模块的空间盲点问题&#xff0c;弥补单通道读写模块在应用中…

spring 查找实现类_69道Spring面试题和答案

什么是spring&#xff1f;Spring是个java企业级应用的开源开发框架。Spring主要用来开发Java应用&#xff0c;但是有些扩展是针对构建J2EE平台的web应用。Spring框架目标是简化Java企业级应用开发&#xff0c;并通过POJO为基础的编程模型促进良好的编程习惯。使用Spring框架的好…

2018全国计算机音乐大赛一等奖,2018全国数字音乐大赛总决赛精彩无限!小学员的技能震惊评委!...

2018全国数字音乐大赛总决赛精彩无限&#xff01;小学员的技能震惊评委&#xff01;近日&#xff0c;为期四天的第三届罗兰艺术节暨全国青少年数字音乐大赛总决赛于北京隆重召开并圆满闭幕。期间丰富多彩的音乐竞技赛事与演艺现场&#xff0c;让所有参与大赛的学员与家长们赞叹…

erp开发和java开发区别_Java程序员求职必学:Spring boot学习指南!

黑马程序员上海中心学姐微信&#xff1a;CZBKSH关注咳咳&#xff0c;今天学姐就来和你们说说Spring对于Java程序员的重要性。首先&#xff0c;Spring 官网首页是这么介绍自己的——“Spring: the source for modern Java”&#xff0c;这也意味着 Spring 与 Java 有着密切的关系…

irobot擦地机器人故障_33款扫地机器人口碑:售价6350元的戴森口碑垫底,小米、科沃斯谁更好用?...

市面上的扫地机器人琳琅满目&#xff0c;消费者该怎么选呢&#xff1f;2020年6月&#xff0c;《消费者报道》汇总了京东、天猫、苏宁上热销的33款扫地机器人的评价情况&#xff0c;对约3.9万条消费者评价数据进行了分析和评分。评价品牌科沃斯、海尔、iRobot、小米、浦桑尼克、…

专业显卡打游戏测试软件,专业显卡能玩游戏吗?专业卡游戏实测

专业显卡能玩游戏吗&#xff1f;专业卡游戏实测2013年03月06日 00:12作者&#xff1a;汤炜炜编辑&#xff1a;汤炜炜分享泡泡网显卡频道3月6日 图形工作站专业显卡与消费级游戏显卡在硬件上是完全一样的&#xff0c;区别只在于规格微调、驱动不同、软件验证。我们也知道&#x…

python集合运算符_Python 集合、字典、运算符

先区分一下序列类型和散列类型&#xff1a; 序列类型&#xff1a;list、string、tuple&#xff0c;他们中的元素是有序的。 散列类型&#xff1a;set、dict&#xff0c;他们中的元素无序的。&#xff08;注意&#xff1a;python3.7.0开始字典变成"有序"了&#xff09…

wireshark 查看端口是否正常_网络抓包软件-Wireshark使用分享

Wireshark(以前叫Ethereal)是一个网络封包分析软件。网络封包分析软件的功能是抓取网络封包&#xff0c;并尽可能地显示出详细的网络封包信息。Wireshark使用WinPCAP作为接口&#xff0c;直接与网卡进行数据报文交换。基础界面介绍Wireshark软件界面有以下几个功能区域&#xf…

普通计算机用的是什么屏幕,笔记本屏幕的色域 72%NTSC和100%sRGB有什么区别

笔记本屏幕的色域 72%NTSC和100%sRGB有什么区别2018-09-15 11:00:05247点赞724收藏60评论无论是选购普通笔记本还是游戏本&#xff0c;大家除了关心产品的外观和配置外&#xff0c;越来越多的朋友把重点放在了屏幕上&#xff0c;也许TN屏和IPS屏大家很好判断哪个更好&#xff0…

redis源码分析 ppt_【Redis】redis各类型数据结构和底层实现源码分析

一、简介和应用Redis是一个由ANSI C语言编写&#xff0c;性能优秀、支持网络、可持久化的K-K内存数据库&#xff0c;并提供多种语言的API。它常用的类型主要是 String、List、Hash、Set、ZSet 这5种。Redis在互联网公司一般有以下应用:String&#xff1a;缓存、限流、计数器、分…

cv图像翻转_涨点技巧!汇集13个Kaggle图像分类项目的性能提升指南

从数据预处理、增强、模型优化、调参再到损失函数...本文转载自&#xff1a;AI公园作者&#xff1a;Prince Canuma 编译&#xff1a;ronghuaiyang注&#xff1a;文末附CV学习交流群导读覆盖了模型相关的方方面面&#xff0c;从数据准备到模型的推理&#xff0c;每个阶段的方法和…

miniui datagrid 隐藏列默认赋值_Qt商业组件DataGrid:内置视图和布局详解(一)

QtitanDataGrid是Qt的商业DataGrid组件&#xff0c;它为将表格数据呈现给最终用户提供了真正的非凡可能性。该组件吸收了用于显示表格的用户界面构造领域中的所有现代成就。目前&#xff0c;这是Qt市场上唯一具有如此令人印象深刻的高级功能和出色性能的网格组件。QtitanDataGr…