STLport::hash_map实现分析

QML 地图修改插件源码(三),Map在Plugin中设置加载地图类型 常用的地图种类分为交通图,地形图,卫星图等等,在QML的Map(以OSM地图为例)中提供activeMapType属性用于读取当前显示的地图类型(注意:该属性为只读属性,不能用于赋值),QML中地图的加载寻找根据瓦片的文件名进行查找,例如名为"osm_100-l-5-2-2-3.png"的瓦片,其中5代表了该地图的类型 阅读详情


前言:

       最近研究了下STLport::hash_map的底层实现。由于STLport是基于SGI实现的,所以也可以通过STLport大致一窥SGI的内部逻辑。

STLport下载地址如下:

http://nchc.dl.sourceforge.net/project/stlport/STLport/STLport-5.2.1/STLport-5.2.1.tar.bz2


叙个大概,几个层次

       以最简单的插入数据来讲,从顶向下,主要stlp_std::hash_map,stlp_std::hashtable和stlp_std::slist三层:

stlp_std::hash_map包装了底层实现,向上层提供服务,stlp_std::hashtable提供对任何有名项的存取操作和删除操作,stlp_std::slist负责对最底层的数据的保存和链表的操作。首先准备一个简单的stlp_std::hash_map应用:

#include <iostream>
#include <hash_map>

typedef stlp_std::hash_map<char , std::string> HASHMAP;
typedef HASHMAP::iterator HASHMAP_ITER;


int main(int argc ,char *argv[])
{

	HASHMAP hsmp;
	hsmp['a'] = "yoshino";
	hsmp['b'] = "akaki";
	hsmp['b'] = "namiwa";
	hsmp['c'] = "akitsushima";
	hsmp['d'] = "iwashima";
	hsmp['e'] = "hashitachi";
	hsmp['f'] = "matsushima";
	
	return 0;
}

这Code没什么特别的,就是向hsmp添加键值。hsmp也是简单至极,只有<key , Value>


stlp_std::hash_map:

我们在声明hsmp时没有指定hash函数和比较函数,所以stlp_std::hash_map的构造中会默认指定(注意,这里我的键是普通类型char,如果是复合类型如结构或是类的话,就必须自定义指定hash函数和比较函数):

hash_map() : _M_ht(0, hasher(), key_equal(), allocator_type()) {}
这里指定的hash函数hasher()和键比较函数key_equal(),都是仿函数,分别定义在:
//_hash_fun.h
_STLP_TEMPLATE_NULL struct hash<char> {
	size_t operator()(char __x) const { return __x; }//返回char的ASCII
};
//_function_base.h
struct equal_to : public binary_function<_Tp, _Tp, bool> {
	bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }
};
在_hash_fun.h中,定义了很多普通类型的hash函数,这里的struct hash<char>就是其中之一,它仅仅是将char值以ASCII值返回。如果你要使用复合类型,就要自己定义hash函数拉,这也是hash_map麻烦的地方。

程序执行添加键值是调用了stlp_std::hash_map的[]重载:

_STLP_TEMPLATE_FOR_CONT_EXT
  _Tp& operator[](const _KT& __key) {
    iterator __it = _M_ht.find(__key);  //搜索键

    return (__it == _M_ht.end() ?  //如果没有搜索到键就执行插入,如果键已存在则返回值
      _M_ht._M_insert(value_type(__key, _STLP_DEFAULT_CONSTRUCTED(_Tp))).second : //hashtable _M_ht;
      (*__it).second );
  }

stlp_std::hash_map中定义了stlp_std::hashtable_M_ht,由它实际处理。在stlp_std::hash_map的[]重载中,首先在_M_ht中搜索键是否存在,如果不存在,则由_M_ht新建插入,如果存在,则返回值。我们新插入键值对<'a',"yoshino">。所以执行hashtable<...>::_M_insert(const value_type& __obj)。程序由此进入stlp_std::hashtable层。


stlp_std::hashtable

hash table表格内的元素称之为(bucket),侯捷先森《STL源码剖析》中介绍了开链法实现的hashtable,如图

而在STLport中,hashtable的实现如下:


其中_BucketVector中的每个元素保存有链表_Slist_node相应的节点的指针。在stlp_std::hashtable定义了bucket:

//typedef slist<value_type, _All> _ElemsCont;
_ElemsCont            _M_elems;	 //bucket list
//typedef _STLP_PRIV _Slist_node_base _BucketType;
//typedef vector<_BucketType*, _BucketAllocType> _BucketVector;  
_BucketVector         _M_buckets;  //存储桶
size_type             _M_num_elements; //标示hashtable元素个数,size()函数返回的即是此

其中_M_buckets就是存储桶,他是个vector数组,每个元素又是_Slist_node_base链表(图中的bucket list)。其中_Slist_node,_Slist_node_base等数据结构将在后面的stlp_std::slist介绍。之前在stlp_std::hash_map程序走到了_M_insert(const value_type& __obj),其实现如下:

template <class _Val, class _Key, class _HF,
          class _Traits, class _ExK, class _EqK, class _All>
__reference__
hashtable<_Val,_Key,_HF,_Traits,_ExK,_EqK,_All>
  ::_M_insert(const value_type& __obj) {
  _M_enlarge(_M_num_elements + 1);  //检查最大负载因子时的桶容量,如果这个容量大于当前桶容量,则需要扩展桶容量
  return *insert_unique_noresize(__obj).first; //在不需要重建表格的情况下插入新节点。键值不允许重复
}
这里涉及到hashmap中的一个概念——负载因子。负载因子=当前数据量/桶的总容量,实际就是占用空间的百分比。这里的_M_enlarge负责检查是否需要扩展桶容量,其实现如下:
template <class _Val, class _Key, class _HF,
          class _Traits, class _ExK, class _EqK, class _All>
void hashtable<_Val,_Key,_HF,_Traits,_ExK,_EqK,_All>
::_M_enlarge(size_type __to_size){ // __to_size = _M_num_elements + 1
  size_type __num_buckets = bucket_count(); //桶个数
  size_type __num_buckets_hint = (size_type)((float)__to_size / max_load_factor()); //散列表中节点的数目除以基本区域能容纳的节点数
  //max_load_factor()获取最大负载因子 , __num_buckets_hint代表获取最大负载因子时的HashMap的总容量,此时__num_buckets_hint处在最小状态
  if (__num_buckets_hint <= __num_buckets) {
    return;   //如果最大负载因子的HashMap的总容量小于当前容量,则什么也不做
  }
  __num_buckets = _STLP_PRIV _Stl_prime_type::_S_next_size(__num_buckets_hint);//返回桶大小下至要求尺寸

  _M_rehash(__num_buckets);  //无条件地改变桶容量为__num_buckets
随后,程序执行hashtable<...>::insert_unique_noresize(const value_type& __obj):
template <class _Val, class _Key, class _HF,
          class _Traits, class _ExK, class _EqK, class _All>
pair<__iterator__, bool>
hashtable<_Val,_Key,_HF,_Traits,_ExK,_EqK,_All>
  ::insert_unique_noresize(const value_type& __obj) {
  const size_type __n = _M_bkt_num(__obj); //获取捅号
  _ElemsIte __cur(_M_buckets[__n]);
  _ElemsIte __last(_M_buckets[__n + 1]);

  if (__cur != __last) {
    for (; __cur != __last; ++__cur) {
      if (_M_equals(_M_get_key(*__cur), _M_get_key(__obj))) {
        //We check that equivalent keys have equals hash code as otherwise, on resize,
        //equivalent value might not be in the same bucket
        _STLP_ASSERT(_M_hash(_M_get_key(*__cur)) == _M_hash(_M_get_key(__obj)))
        return pair<iterator, bool>(iterator(__cur), false);
      }
    }
    /* Here we do not rely on the _M_insert_noresize method as we know
     * that we cannot break element orders, elements are unique, and
     * insertion after the first bucket element is faster than what is
     * done in _M_insert_noresize.
     */
    __cur = _M_elems.insert_after(_ElemsIte(_M_buckets[__n]), __obj);
    ++_M_num_elements;//累加vector线性表元素个数
    return pair<iterator, bool>(iterator(__cur), true);
  }

  return pair<iterator, bool>(_M_insert_noresize(__n, __obj), true);
}

在insert_unique_noresize里,我们首先要计算出一个桶号,作为桶中存储的下标。方法是执行_M_bkt_num , 参数是键值对__obj,_M_bkt_num最终调用到_M_bkt_num_key。执行_M_hash(__key) % __n,求出桶号,这里的_M_hash就是之前在stlp_std::hash_map中默认指定的struct hash<char>。所以,这里的算出的桶号就是我们制定的char键的ASC码和桶容量做求模运算。

size_type _M_bkt_num_key(const _KT& __key) const
{ return _M_bkt_num_key(__key, bucket_count()); }

size_type _M_bkt_num(const value_type& __obj) const
{ return _M_bkt_num_key(_M_get_key(__obj)); }

_STLP_TEMPLATE_FOR_CONT_EXT
size_type _M_bkt_num_key(const _KT& __key, size_type __n) const
{
     return return _M_hash(__key) % __n;   //_M_hash(__key) % bucket_count()求出桶号
}

获取桶号后,程序检查是否有相同hash码的等效key,防止它们处在同一个桶中。随后执行hashtable<...>::_M_insert_noresize(size_type __n, const value_type& __obj):

template <class _Val, class _Key, class _HF,
          class _Traits, class _ExK, class _EqK, class _All>
__iterator__
hashtable<_Val,_Key,_HF,_Traits,_ExK,_EqK,_All>
  ::_M_insert_noresize(size_type __n, const value_type& __obj) {   //__n代表算出的捅号 , __obj是键值对
  //We always insert this element as 1st in the bucket to not break
  //the elements order as equal elements must be kept next to each other.
  size_type __prev = __n; //桶号
  _ElemsIte __pos = _M_before_begin(__prev)._M_ite;                  //_pos根据桶号__prev获取插入点iter
  //填充数据节点到_M_buckets.begin() + __prev
  fill(_M_buckets.begin() + __prev, _M_buckets.begin() + __n + 1,
      _M_elems.insert_after(__pos, __obj)._M_node);   //_M_elems是slist,在__pos的下一个位置插入新节点,值为__obj,这里的最终是新节点,数据最终存储到slist::_M_head _Slist_node_base _M_node
  ++_M_num_elements; //累加捅数目
  return iterator(_ElemsIte(_M_buckets[__n]));                              //返回新捅号数据的迭代
}

这里传给_M_insert_noresize两个参数,__n代表算出的捅号 , __obj是键值对。然后由__pos根据桶号获取下一个插入位置,_M_elems.insert_after将键值对__obj插入到位置__pos之后。fill填充将链表头填充到_M_buckets.begin() + __prev,也就是桶号位置。我们在程序中首先插入['a',"..."],获取桶号6,则在_BucketVector::_M_start接上['a',"..."](插入到链表_Slist_node,具体介绍后面介绍),当插入['b',"..."]时,获取桶号0,则将['b',"..."]插入['a',"..."]之前,作为_BucketVector::_M_start中的第一个元素。随后再插入['c',"..."],获取桶号2,则将['c',"..."]插入['b',"..."]之后。以此类推,如下图:



stlp_std::slist:

hashtable将数据存储存储在_M_elems,它是个slist类型。在这层,主要根据上层的指令,代理完成链表的相关操作,下面介绍几个相关的数据类型:

//单向链表的节点基本结构
struct _Slist_node_base {
	_Slist_node_base* _M_next;
};

//单向链表的节点结构
template <class _Tp>
class _Slist_node : public _Slist_node_base {
public:
        _Tp _M_data;              //键值对最后存储在这里
        __TRIVIAL_STUFF(_Slist_node);
};

template <class _Tp, class _Alloc>
class _Slist_base {
        //...
        typedef _STLP_alloc_proxy<_Slist_node_base, _Node, _M_node_allocator_type> _AllocProxy;
        //...
        _AllocProxy _M_head;
};


template <class _Tp, class _Alloc>
class slist : protected _STLP_PRIV _Slist_base<_Tp,_Alloc>
{
        //...
};
首先可见 _Slist_node_base和继承它的_Slist_node ,键值对 数据就最终存储在 链表结构_Slist_node:: _M_data ,各个键值对之间通过 _Slist_node_base :: _M_next 关联

由_M_elems.insert_after(__pos, __obj)进入,它最终执行:

__STATIC_CAST(_Node*, _STLP_PRIV __slist_make_link(__pos, _M_create_node(__x)));
这里我们调用 _M_create_node(__x)建立一个新的_Slist_node实例(_Slist_node_base派生) ,然后执行 __slist_make_link 将这个新实例插入到 __pos之后, _M_create_node 实现如下:

_Node* _M_create_node(const value_type& __x) {
   //typedef _STLP_PRIV _Slist_node<_Tp> _Node;
    _Node* __node = this->_M_head.allocate(1);      //新建一个node,并分配空间
    _STLP_TRY {
      _Copy_Construct(&__node->_M_data, __x);        //配置新node的数据
      __node->_M_next = 0;                                         //单向链表,end()->0;
    }
    _STLP_UNWIND(this->_M_head.deallocate(__node, 1))
    return __node;                                                        //返回新加的node
  }
这样,新的数据就被建立在 _node中 ,然后_node被插入到__pos之后,这样链表的操作就完成了。回到 stlp_std :: hashtable的hashtable<...>::_M_insert_noresize
fill(_M_buckets.begin() + __prev, _M_buckets.begin() + __n + 1,
      _M_elems.insert_after(__pos, __obj)._M_node);   //_M_elems是slist,在__pos的下一个位置插入新节点,值为__obj,这里的最终是新节点,数据最终存储到slist::_M_head _Slist_node_base _M_node
  ++_M_num_elements; //累加捅数目
  return iterator(_ElemsIte(_M_buckets[__n])); 
_M_elems.insert_after 完成了底层的链表操作, _M_node 返回 _Slist_node_base,由fill将它插入到 _M_buckets.begin() + __prev( __prev 桶号位置 ,然后层层返回指向新加数据的迭代器。这样就完成了stlp_std::hash_map的一次数据添加。


 

        
保姆级教程:用NX机电概念设计与RobotStudio实现ModbusTCP联合仿真(附完整代码) 本文提供了一份详细的NX机电概念设计与RobotStudio联合仿真实战教程。通过ModbusTCP协议构建数据桥梁,实现机械行为与机器人控制逻辑的双向实时同步。文章从环境搭建、信号配置、RAPID代码编写到联合调试,逐步拆解了实现数字孪生核心链路的关键步骤,并分享了常见问题排查方法,助力工程师在虚拟环境中提前验证产线协同。 阅读详情

相关推荐

c++中hash_table以及std::map应用案例

代码重点是hash_table,附加std::map与其做对比,实现的是一条sql语句:select c_nationkey, c_mktsegment, count(*), max(c_acctbal) from aaa_customer_1g group by c_nationkey, c_mktsegment order by c_nationkey, c_mktsegment

错误:'bad_alloc' : is not a member of 'stlp_std'

用ATL实现一个COM时发现编译错误:bad_alloc : is not a member of stlp_std 去除所有代码,找到有问题的代码行。最后可用一行代码重现错误:    #include // for std::bad_alloc    ...    throw std::bad_alloc();禁用STLPort库而使用VC6的原生stl库,没有错误,但有个警告:war

金庆的专栏 3855

1978-2023年中国地级市人口就业与工资数据

1、资源内容地址:https://blog.csdn.net/2301_79696294/article/details/1403115262、代码特点:今年全新,手工精心整理,放心引用,数据来自权威,相对于其他人的控制变量数据准确很多,适合写论文做实证用 ,不会出现数据造假问题3、适用对象:大学生,本科生,研究生小白可用,容易上手!!!3、课程引用: 经济学,地理学,城市规划与城市研究,公共政策与管理,社会学,商业与管理 数据指标说明## 一、中国就业数据1980-2023包括:1.总就业人数2.城镇就业人数3.乡村就业人数4.第一产业就业人数5.第二产业就业人数6.第三产业就业人数注:1990年及以后的劳动力、就业人员数据根据劳动力调查、全国人口普查推算;其中2011-2019年数据是根据第七次全国人口普查修订数。城镇单位数据不含私营单位。2012年行业采用新的分类标准,与前期不可3.覆盖范围:10000多个样本,300多个地级市,面板数据可以直接使用,部分城市2022年指标有一定缺失。4.数据来源:中国城市统计年鉴 、各地市

VC6 下使用 STLPort

VC6下使用STLPort 为了使用hash_map,今天下载了STLport,在VC6下编译成功。 1. STLport下载:http://www.stlport.org/       我下载的是最新版  02.25.07: STLport 5.1.2 relea

Jingle的专栏 1185

STL学习笔记之容器--hashtable

之前对hash一直存在恐惧感,毕竟没用过……最近在一个组件里面自己实现了一个hashtable,感觉也就这么回事;回头看看书上对hashtable的分析,发现是极其的相似。不过,旧版本的C++标准里面并没有hashtable这个东西,而C++11中引入了相关的容器(std::unordered_set, std::unordered_multiset, std::unordered_map, st

wei.zhou的专栏 811

STL — hash_map/hash_set源代码剖析

 hash_map/hash_set源代码剖析虽然STL只规范复杂度与接口,并不规范实现方法,但STL set多半以RB_Tree. SGI 则是在 STL标准之外另又提供了一个所谓的hash_set,以hashTable的底层机制. 由于hash_set所供应的操作接口,hashTable都提供了,所以几乎所有的hash_set操作行为,都只是转调用hashTable的操作行为而已.运用set为...

阿亮的博客 750

慎用Visual Studio C++默认的hash_map

慎用Visual Studio C++默认的hash_map写了一个模块客户端和服务器共用,为了加快查询速度,用了hash_map,今天一个兄弟老卢测试说将其中的hash_map替换成map后速度更快,有点晕。自己写了一段代码在Windows下用Visual Studio C++测试hashmap。首先说明,hashmap目前还没有进入C++的规范,但是大部分厂商都实现了这个容器,测试的h

C++是促进脑死亡的最佳方式 1万+

STLPort 哈希表 hash_map/hash_multimap 删除速度慢

KeyWords: STLPort hash_map hash_multimap erase操作慢 本文使用的STL版本是STLPort5.2.1的最新release版本,配合G++4.5版本使用。我在项目中发现一个性能的瓶颈,最终定位到的原因是使用STLPorthash_multimap(C++11之后哈希表改为unordered_map和unordered_multimap)中erase函...

a363344923的专栏 541

对Visual Studio C++ hash_map严谨一点的测试

对Visual Studio C++ hash_map严谨一点的测试在上次测试完成后,有一个网友mm304321141 (明儿)在留言上对测试数据进行了质疑,先谢谢他的指正。 我的确不知道Visual Studio C++ 默认的Dinkumware STL hash_maphash_compase有两个参数bucket_size 和min_buckets ,我自己实现的时候类是从stdex

C++是促进脑死亡的最佳方式 5488

C++ STL中哈希表 hash_map从头到尾详细介绍

目录(?)[+]0 为什么需要hash_map用过map吧?map提供一个很常用的功能,那就是提供key-value的存储和查找功能。例如,我要记录一个人名和相应的存储...

lianghong1564868的博客 544

慎用Visual Studio C++默认的hash_map----转载

写了一个模块客户端和服务器共用,为了加快查询速度,用了hash_map,今天一个兄弟老卢测试说将其中的hash_map替换成map后速度更快,有点晕。自己写了一段代码在Windows下用Visual Studio C++测试hashmap。 首先说明,hashmap目前还没有进入C++的规范,但是大部分厂商都实现了这个容器,测试的hashmap有两种: l          Visual St...

weixin_34248258的博客 572

hash_map哈希映照容器

hash_map哈希映照容器 一、原理 与map相似,都将记录型的元素分为键值和映照数据两个部分,插入元素不允许重复。不同之处是hash_map用哈希表实现,而map用红黑树;前者检索用迭代器遍历,后者用二分法。所使用的哈希函数对象和键值相等比较函数,默认为hash<key>和equal_to<key>。值插入,用insert_equal,值唯一,所以可以用[]操作符(...

hopegrace的博客 377

对Visual Studio C++ hash_map严谨一点的测试--转载

在上次测试完成后,有一个网友mm304321141 (明儿)在留言上对测试数据进行了质疑,先谢谢他的指正。 我的确不知道Visual Studio C++ 默认的Dinkumware STL hash_maphash_compase有两个参数bucket_size 和min_buckets ,我自己实现的时候类是从stdext::hash_compare继承的,忽视了这两个参数,而上次测试的时候...

weixin_33694620的博客 260

c++错误之string C2679

error C2679: 二进制“+”: 没有找到接受“std::string (__cdecl *)(void)”类型的右操作数的运算符(或没有可接受的转换)   看到解释仔细读一下就会发现: 他说的是:  操作符"+"的右操作数的类型和函数指针不匹配.  仔细看不难发现后面接着是函数指针   现象:msg = msg + get_current_date; 而我的get_curren

bai_lu_sz的专栏 2282

VS2010编译调试STL源代码

平台环境:windows 7 32位+VS2010+STL源代码

KangRoger的专栏 6571

STL源码分析hash_multimap配接器

前言 上节分析hash_map知道它是不允许存在相同的键存在, 本节的hash_multimap就可以允许存在多个相同的键, 以hashtable为接口, 这里我就只分析hash_map的不同点吧, 毕竟很多都是一样的了, 没有必要重复的分析相同的代码. insert 最大的区别就在这里, hash_multimap 是以insert_equal为接口, 所以支持插入重复的键. #ifndef...

啦啦啦 550
上一篇: 多台,不同OS的PC机如何共享鼠标键盘
下一篇: 将ffmpeg编入motion
古美門直樹
博客等级 码龄15年 31粉丝 27原创
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值