字典哈希表的实现原理_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,一经查实,立即删除!

相关文章

ddl是什么意思网络语_DDL是什么意思?DDL跟数据库关系介绍 常见的DDL语句分享...

数据库模式定义语言DDL(Data Definition Language)&#xff0c;是用于描述数据库中要存储的现实世界实体的语言。这些定义包括结构定义、操作方法定义等。数据库模式定义语言并非程序设计语言&#xff0c;DDL数据库模式定义语言是SQL语言(结构化查询语言)的组成部分。SQL语言包…

内蒙古科技大学计算机专业分数线,内蒙古科技大学

编号省份批次投档单位科类计划性质省控线专业国标码专业投档志愿录取人数最高分最低分平均分最低分与控制线差49重庆市本科第二批1502_本2_理_原始计划:64理工类非定向435050262商务英语12496.18493.16494.675849重庆市本科第二批1502_本2_理_编号省份批次投档单位科类计划性质…

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…

ios html清除缓存,iOS开发之1行代码实现缓存计算及清除缓存

话不多说&#xff0c;直接撸代码//// gzhCache.h// cache//// Created by 郭志贺 on 2020/5/27.// Copyright © 2020 郭志贺. All rights reserved.//#import NS_ASSUME_NONNULL_BEGINinterface gzhCache : NSObject/// 计算缓存大小(float)filePath;/// 清理缓存(void)cl…

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

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

python使用pkg包_Python deb-pkg-tools包_程序模块 - PyPI - Python中文网

python包deb-pkg-tools是要构建和检查Debian binary packages和二进制包的存储库。它的主要用例是自动化构建。一些功能在命令行界面中公开(如下所述)因为在shell脚本中使用非常方便&#xff0c;而其他功能将用作python api。包当前在cpython上测试2.6、2.7、3.4、3.5、3.6、3.…

有ul没有字html,HTML ul 标签

定义和用法标签定义无序列表。实例无序 HTML 列表&#xff1a;CoffeeTeaMilkTIY (请在页面底部查看更多实例)浏览器支持所有主流浏览器都支持 标签。HTML 与 XHTML 之间的差异在 HTML 4.01 中&#xff0c;ul 元素的 "compact" 和 "type" 属性是不被赞成使用…

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

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

snowflake做主键 自增_自增ID算法snowflake - C#版

急景流年&#xff0c;铜壶滴漏&#xff0c;时光缱绻如画&#xff0c;岁月如诗如歌。转载一篇博客来慰藉&#xff0c;易逝的韶华。使用UUID或者GUID产生的ID没有规则Snowflake算法是Twitter的工程师为实现递增而不重复的ID实现的概述分布式系统中&#xff0c;有一些需要使用全局…

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

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

html5的狭义概念,“资源”这一概念,可以有狭义和广义两种理解。狭义的资源是指...

【原文】“资源”这一概念&#xff0c;可以有狭义和广义两种理解。狭义的资源是指人类生产活动所需要的、在自然界存在的物质(材料)和动力的天然来源。广义的资源&#xff0c;则是指人类用来帮助从事一定活动、以达到一定目的的一切要素和有利条件的总和&#xff0c;简单地说&a…

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;让所有参与大赛的学员与家长们赞叹…

python3.8爬虫_python爬虫系列(3.8-正则的使用)

一、需要系统的学习正则表达式1、元字符1..:除了\n以外的任意字符2.*:出现0到多次3.?:出现0或者1次4.:表示出现1到多次2、常用的方法1.compile:表示生成正则表达式参考地址2.findall:查找全部注意返回的是一个列表参考地址import reimport requestsclass GuShiWen(object):def…

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

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