SGI-STL简记(八)-哈希关联容器(hash、hash_set、hash_map、hash_multiset、hash_multimap)
stl_hash_fun.h : hash:模板函数对象类类型,一般作为hash_set、hash_map、hash_multiset、hash_multimap容器的默认哈希函数,目前提供了多个特化版本,并重载实现operator(),参数类型有char、unsigned char、char*、int等 内置可转化为整型的数据类型的版本;除const char*和char*使用了__stl_hash_string计算哈希值,其余的则直接返回参数value值作为哈希值;哈希值返回值类型为size_t; __stl_hash_string:遍历各个字符串元素,累积遍历计算哈希值,计算方式为h=0;h=5*h+*s,*s为遍历字符串相应元素,h即为最后的哈希值,类型为unsigned long; 对于其他的hash模板实例实现,如basic_string<_CharT,_Traits,_Alloc>、crope、wrope或者是用户自定义的hash模板函数对象实现; stl_hashtable.h : _Hashtable_node:哈希表节点模板类; 数据成员: _M_next:指向下一个哈希表节点的指针; _M_val:保存当前哈希表节点的值(哈希值); _Hashtable_iterator/_Hashtable_const_iterator:哈希表迭代器模板类,模板参数_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc分别为值类型、键类型、哈希函数、萃取键类型、相等键类型、内存分配类型; 内部重声明了forward_iterator_tag前向迭代器类型,以及value_type、指针、引用等类型; 数据成员: _M_cur:当前迭代器所指向容器的所在节点指针; _M_ht:迭代器所迭代器的容器对象指针; 成员函数: _Hashtable_iterator构造函数初始化_M_cur、_M_ht成员; 此外重载的operator*和operator->解引用和取指针内部返回当前_M_cur节点的值_M_val及其地址; 重载的operator==、operator!=直接比较_M_cur指针地址; 而重载的operator++、operator++(int)实现前向迭代; 迭代方式为:取得_M_cur的_M_next下一个节点,若下一个节点不为空则直接返回,否则while循环查找当前节点元素所在的槽(桶)后的不为空的桶,且设置当前迭代器指向新桶的第一个节点;(因部分桶可能没有节点, 查找下一个节点时需遍历查找下一个节点所在不为空的桶); hashtable:哈希表模板类,作为hash_set、hash_map、hash_multiset、hash_multimap哈希容器的底层实现;模板参数同_Hashtable_iterator迭代器模板参数; 辅助工具函数或全局变量: __stl_prime_list:预先填充的桶数数组列表数,预先设置__stl_num_primes为28,即28组,__stl_prime_list内部值桶数量(质数)基本上是按照2倍增长的方式,至少会分配53个桶,至多可分配4294967291个桶; __stl_next_prime:返回至少可容纳指定值桶数n时__stl_prime_list列表中对应的预分配桶数; 哈希表模板类重声明了key_type、value_type、指针、引用等其他的常规类型,此外还有hasher(哈希函数对象)、key_equal(键相等比较函数对象)、迭代器等类型; 数据成员: _M_hash:哈希函数对象; _M_equals:键相等比较函数对象; _M_get_key:萃取键函数对象; _M_buckets:vector<_Hashtable_node<Val>* >类型的桶容器对象,主要用于保存指向相同hash_key下的节点链表首节点(即同一个桶下各个元素具有相同的key值),这个结构有点儿类似于deque底层内部布局的结构,不过 哈希表的某些桶的节点数据可能为空且不一定是连续,而deque是连续的(迭代器指针范围区间内),此外哈希表的桶下各个节点缓冲区不一定是连续的,各个桶的缓冲区也不一定连续,而deque的node节点缓冲区是连续的, 各个node不一定连续); _M_num_elements:容器中保存的实际节点元素个数; _M_node_allocator:节点分配器对象 成员函数: hash_funct:返回_M_hash对象; key_eq:返回_M_equals对象; get_allocator:返回当前内存节点分配器对象_M_node_allocator; 构造函数:提供初始化桶数量n、哈希函数hf、相等比较函数eql、键萃取函数ext以及内存分配对象a以初始化哈希表的相应数据成员;内部调用_M_initialize_buckets执行实际的初始化桶的操作; _M_initialize_buckets:初始化桶,内部调用_M_next_size获取实际至少可分配的桶数并调整_M_buckets容器大小并初始化_M_num_elements为0; _M_next_size:获取实际至少可分配的桶数,内部调用__stl_next_prime获取; 拷贝构造函数:通过源容器初始化相应的数据成员,此外也调用了_M_copy_from拷贝各个桶下的数据至当前哈希表容器; _M_copy_from:内部重新调整_M_buckets桶容器大小;内部循环遍历各个桶以及各桶下的所有节点至当前容器,其具体操作为:调用_M_new_node创建各个桶下的节点并采用首插法的方式添加该节点至当前容器相应的桶下构成单链表。 _M_new_node:内部调用_M_get_node申请一个节点的缓冲空间并调用construct构造指定值obj的节点; _M_get_node/_M_put_node:申请/释放一个节点大小的缓冲区内存空间; 复制拷贝函数:先调用clear清空当前容器元素,并通过源初始化相应数据成员,此外调用_M_copy_from拷贝源容器各个桶下的节点元素; 析构函数:内部调用clear清空当前容器所有元素; clear:清空当前容器所有元素,其内部遍历各个桶以及桶下的各个链表节点并调用_M_delete_node销毁该节点,此外将桶容器清空(只是简单的修改桶容器元素为0),_M_num_elements调整为0; _M_delete_node:内部调用destroy销毁析构该节点对象,此后调用_M_put_node释放该节点内存缓冲区; size:返回当前容器元素个数(返回_M_num_elements); max_size:返回当前容器可容纳元素最大容量(返回size_type(-1)); empty:当前容器是否为空; swap:交换两个哈希表容器元素,内部只是简单的交换哈希表哈希函数对象_M_hash、相等比较函数对象_M_equals、键萃取函数对象_M_get_key、桶容器_M_buckets、以及容器元素个数_M_num_elements; begin:返回哈希表容器的首迭代器(事实上是返回第一次出现桶下的节点不为空的首节点构造的迭代器对象); end:返回哈希表容器的尾迭代器(返回由空节点构造的迭代器对象即可); bucket_count:返回当前桶容器的容量,也即桶的个数; max_bucket_count:返回最大的桶容量大小(返回__stl_prime_list[(int)__stl_num_primes-1]); elems_in_bucket:返回某个指定桶下的元素个数,注意参数bucket不可超过bucket_count,否则可能抛出异常; resize:重新调整哈希容器中桶以及桶下节点布局;目前的做法是若重置元素大小__num_elements_hint大于当前容器的桶数,则重新分配申请一个桶容器,并将原桶容器下的各个桶节点重新计算放置到 各个新的桶下(还是采用首插法)直至完成,此后调用swap交换桶容器即可,最后产生的影响为桶容器及其容量改变、桶下的各个节点元素顺序被改变,若若重置元素大小__num_elements_hint小于当前容器 的桶数则不做任何处理; insert_unique_noresize:插入唯一值不改变桶容器大小操作;内部先调用_M_bkt_num获取到当前插入值可放置的桶位置,此后遍历当前桶下的所有节点通过调用相等比较函数对象_M_equals以及_M_get_key 萃取到键key进行比较,若相等则意味着已存在相应key,则插入失败;若均为找到相等的key的元素,则调用_M_new_node创建该插入节点并通过首插法放入计算得到的可放置桶位置下; _M_bkt_num:获取当前元素值所在的桶;内部使用键萃取函数对象_M_get_key获取到key并调用_M_bkt_num_key返回当前所在的桶; _M_bkt_num_key:内部以key作为参数,调用哈希函数对象_M_hash计算得到的哈希值,该哈希值与当前桶容器个数求余即为所在的桶位置,故哈希函数对象的设计会影响桶、数据元素布局; insert_unique:插入唯一值,提供重载版本实现; 其中插入指定的值版本为内部先调用resize(_M_num_elements + 1)调整桶布局,此后调用insert_unique_noresize插入唯一值不改变桶容器大小操作; 输入迭代器参数范围版本则根据数据类型萃取迭代器类型,调用输入迭代器或前向迭代器的不同版本实现,输入迭代器版本则for遍历调用insert_unique插入迭代器范围的值,前向迭代器版本内部先通过 distance获取到输入迭代器范围长度,并调用resize调整桶容器,此后循环遍历调用insert_unique_noresize插入输入迭代器范围的值;此两个版本也是出于效率考虑; insert_equal:插入相等元素值,提供了多个重载版本实现;做法类似于insert_unique,唯一不同的是insert_unique内部调用insert_unique_noresize插入唯一值,而insert_equal调用insert_equal_noresize 插入相等的元素值,以支持多元素保存插入; insert_equal_noresize:插入相等元素值不改变桶容器大小操作,同insert_unique_noresize类似,只是内部当找到相等的key的元素时会创建插入节点并放置到(首插法)相应的桶下而不是返回插入失败的操作, equal_range:返回与指定key相等的容器元素迭代器范围,内部通过调用_M_bkt_num_key获取到指定key对应的桶位置,遍历桶位置以及该位置以后的位置通过_M_equals判断key是否相等直到不相等时间的迭代器范围; erase:移除指定迭代器位置的元素或某key、某迭代器范围的元素几种重载版本; 移除指定key的元素操作为移除所有相等key的元素,其内部操作为调用_M_bkt_num_key获取当前key所在桶位置,遍历该桶下的所有 节点,通过_M_equals比较键key是否相等,若相等则调用_M_delete_node移除该节点并调整其他节点间的关系指针指向(内部的移除操作过程为:先从该桶下的第二个节点开始依次比较移除并调整节点指向,此后再处理 第一个节点); 移除指定迭代器位置的版本为,通过_M_bkt_num获取到所在桶位置,移除指定位置下的节点并调整节点关系指向即可; 移除迭代器范围的版本为,获取输入迭代器开始和结束的所在桶位置,并调用_M_erase_bucket移除桶间的节点; _M_erase_bucket:移除指定n桶位置下的节点,有多个重载版本实现,一种移除指定迭代器范围的元素,一种移除迭代器所在桶的所有元素,内部调用_M_delete_node移除相应节点; find:查找指定key的容器元素,内部调用_M_bkt_num_key获取key所在桶位置,并依次遍历桶下的节点,调用_M_equals比较key,若找到则返回该构造的迭代器对象,否则返回失败时迭代器对象; find_or_insert:查找指定的obj值,若当前存在容器中,则返回对应的值,否则插入当前值至容器中; 此外重载的operator==操作符,其当两个哈希表容器中桶容器大小相同、各个桶下的各个节点值对应相等且各个桶下的节点个数也要相等才返回true,否则均返回false; stl_hash_set.h : hash_set:hash_set模板容器,模板参数_Value、_HashFcn、_EqualKey、_Alloc分别对应值类型、哈希函数对象类型、相等键比较函数对象类型、以及内存分配函数; 默认的哈希函数为hash<_Value>、相等键比较函数为equal_to<_Value>(内部重载operator()直接的值比较)、分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc(malloc_alloc(即__malloc_alloc_template<0>) 或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器; 内部重声明了各种常用的数据类型,迭代器、指针、引用等类型; 数据成员: _M_ht:哈希表对象,其作为hash_set的底层实现,此时哈希表模板参数_Key和_Val均为_Value,键值萃取参数为_Identity<_Value>(内部重载operator()直接返回参数value作为萃取的key); 成员函数: 构造函数:提供了多种构造函数实现,包括初始化桶个数、哈希函数、键相等比较函数、内存分配器等,其中一个提供初始化桶个数为100个(事实上内部底层为193个) 其他的对外接口基本上是直接调用哈希表对象_M_ht的相应名称的接口或类似接口实现; hash_multiset:同hash_set,但可以保存相同的key的元素;最根本的区别在于insert操作,hash_set内部调用的是insert_unique,而hash_multiset则是insert_equal; 此外还有针对hash_set、hash_multiset实现的插入迭代器insert_iterator(主要实现重载的operator=操作(内部调用容器的insert实现插入操作)),可方便的实现支持某些算法操作; stl_hash_map.h : hash_map:hash_map模板容器,模板参数_Key、_Tp、_HashFcn、_EqualKey、_Alloc分别对应键类型、数据值类型、哈希函数对象类型、相等键比较函数对象类型、以及内存分配函数; 默认的哈希函数为hash<_Key>、相等键比较函数为equal_to<_Key>(内部重载operator()直接的值比较)、分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc(malloc_alloc(即__malloc_alloc_template<0>) 或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器; 内部重声明了各种常用的数据类型,迭代器、指针、引用等类型; 数据成员: _M_ht:哈希表对象,其作为hash_map的底层实现,此时哈希表模板参数_Val为pair<const _Key,_Tp>,键值萃取参数为_Select1st<pair<const _Key,_Tp> >(内部重载operator()返回参数pair的first作为萃取的key); 成员函数: 构造函数:也提供了多种构造函数实现,包括初始化桶个数、哈希函数、键相等比较函数、内存分配器等,其中一个提供初始化桶个数为100个(事实上内部底层为193个) 其他的对外接口基本上是直接调用哈希表对象_M_ht的相应名称的接口或类似接口实现; hash_multimap:同hash_map,但可以保存相同的key的元素;最根本的区别在于insert操作,hash_map内部调用的是insert_unique,而hash_multimap则是insert_equal,另外一些区别便是模板参数以及底层哈希表对象的模板参数类型; 此外同hash_set一样,提供了针对hash_map、hash_multimap的插入迭代器insert_iterator,可方便的实现支持某些算法操作,另外还提供了operator[]操作,其底层实现为find_or_insert; 关联容器中set、map、multimap、multiset的实现核心为底层的红黑树_Rb_tree,哈希关联容器的实现核心则为底层的哈希表hashtable,hashtable相对于红黑树,实现较为简单,插入、移除、查找等操作较为高效, 不过这依赖于哈希函数和数据元素容量,红黑树下时间复杂度为一个固定的时间log(n)。