飞道的博客

C++(STL源码):30---hash table源码剖析(哈希表)

247人阅读  评论(0)
  • 前面介绍的二叉搜索树和平衡二叉搜索树。二叉搜索树具有对数平均时间的表现,但这样的表现构造在一个假设上:输入数据有足够的随机性
  • 文本要介绍一种名为hash table(哈希表/散列表)的数据结构,这种结构在插入、删除、搜索等操作上也具有“常数平均时间”的表现,而且这种表现是以统计为基础,不需依赖输入元素的随机性
  • 哈希表可以在本人的数据结构文章中查看,文本就不再详细介绍了:https://blog.csdn.net/qq_41453285/article/details/103517420https://blog.csdn.net/qq_41453285/article/details/103533372https://blog.csdn.net/qq_41453285/article/details/103534526
  • hash table是作为hash_set、hash_map、hash_multiset、hash_multimap容器的底层实现
  • 并且hash table解决哈希冲突的方式是链地址法(开链)的形式
  • SGI STL的哈希表结构:
    • 哈希表用vector实现
    • vector的一个索引出代表一个桶子(bucket)
    • 每个桶子内含有一串链表,链中有含有节点
  • 下图是以链地址法完成的hash table形式:

一、哈希冲突

  • 哈希表的基本概念就不介绍了,直接介绍哈希冲突

线性探测(Linear Probing)

  • 根据元素的值然后除以数组大小,然后插入指定的位置

  • 主集团现象:在上图中,如果我们插入的新元素为8、9、0、1、2、3中,第一次的落脚点一定在#3上,这就造成了性能的降低,平均插入成本增加,这种现象在哈希过程中称为主集团(primary clustering)

二次探测(quadratic probing)

  • 二次探测用来解决主集团问题的。其解决哈希冲突的方式下列公式:

  • 也就说,如果新元素的起始插入位置为H的话但是H已被占用,那么就会依次尝试、...、+
  • 下图是一个二次探测的例子

  • 二次探测带来一些疑问:
    • ①线性探测每次探测的都必然是一个不同的位置,二次探测能够保证如此?二次探测能否保证如果表格之中没有X,那么我们插入X一定能够成功?
    • ②线性探测的运算过程机器简单,二次探测则显式复杂一些。这是否会在执行效率上带来太多的负面影响
    • ③不论线性探测还是二次探测,当负载稀疏过高时,表格能够够动态成长?
  • 疑惑①:幸运的是,如果假设表格大小为质数,而且永远保持负载系数在0.5以下(也就是说超过0.5就要重新配置表格),那么就可以确定每插入一个元素所需要的探测次数不多于2
  • 疑惑②:至于复杂度问题,一般总是这样考虑:收获的比付出的多,才值得这么做。我们增加了探测次数,所获得的利益好歹比二次函数计算所花的时间多。线性探测需要的是一个加法(加1),一个测试(看是否回头),以及一个可能用到的减法(用以绕转回头)。二次探测需要的则是一个加法(从i-1到i)、一个乘法(计算),另一个加法以及一个mod运算。看起来得不偿失。然而中间却又一些技巧,可以除去耗时的乘法和除法:

  • 因此,如果我们能够以前一个H值来计算下一个H值,就不需要执行二次方所需要的乘法了。虽然还是一个乘法,但那是乘以2,可以位移位快速完成。置于mod运算,也可证明并非真有需要
  • 疑惑③:array的增长。如果想要扩充表格,首先必须要找出下一个新的且足够大(大约两倍)的质数,然后考虑表格重建的成本——不仅要拷贝元素,还需要考虑元素在新表格中的位置然后再插入
  • 二次探测可以消除主集团,却可能造成次集团:两个元素经由哈希函数计算出来的位置若相同,则插入时所探测的位置页相同,形成了某种浪费。消除次集团的方法也有,例如复式散列

开链(链地址法)

  • 所谓的开链,就是在一个表格元素中维护一个链表
  • 使用这种方法,表格的负载系数将大于1
  • SGI STL的哈希表便是采用这种做法

二、hash table的节点定义(_Hashtable_node)


  
  1. template < class _Val>
  2. struct _ Hashtable_node
  3. {
  4. _Hashtable_node* _M_next;
  5. _Val _M_val;
  6. };

三、hash table的迭代器(_Hashtable_iterator )

  • 下面是迭代器的定义:
    • 迭代器必须永远维系着与整个“bucket vector”的关系,并记录目前所知的节点

  
  1. template < class _Val, class _Key, class _HashFcn,
  2. class _ ExtractKey, class _ EqualKey, class _ Alloc>
  3. struct _ Hashtable_iterator {
  4. typedef hashtable<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc>
  5. _Hashtable;
  6. typedef _Hashtable_iterator<_Val, _Key, _HashFcn,
  7. _ExtractKey, _EqualKey, _Alloc>
  8. iterator;
  9. typedef _Hashtable_const_iterator<_Val, _Key, _HashFcn,
  10. _ExtractKey, _EqualKey, _Alloc>
  11. const_iterator;
  12. typedef _Hashtable_node<_Val> _Node;
  13. typedef forward_iterator_tag iterator_category;
  14. typedef _Val value_type;
  15. typedef ptrdiff_t difference_type;
  16. typedef size_t size_type;
  17. typedef _Val& reference;
  18. typedef _Val* pointer;
  19. _Node* _M_cur; //迭代器目前所指节点
  20. _Hashtable* _M_ht; //保持对容器的连结关系(因为可能需要从bucket跳到bucket)
  21. _Hashtable_iterator(_Node* __n, _Hashtable* __tab)
  22. : _M_cur(__n), _M_ht(__tab) {}
  23. _Hashtable_iterator() {}
  24. reference operator*() const { return _M_cur->_M_val; }
  25. #ifndef __SGI_STL_NO_ARROW_OPERATOR
  26. pointer operator->() const { return &( operator*()); }
  27. #endif /* __SGI_STL_NO_ARROW_OPERATOR */
  28. iterator& operator++();
  29. iterator operator++( int);
  30. bool operator==( const iterator& __it) const
  31. { return _M_cur == __it._M_cur; }
  32. bool operator!=( const iterator& __it) const
  33. { return _M_cur != __it._M_cur; }
  34. };
  • 迭代器没有后退操作(operator--),也没有定义所谓的你想迭代器(reverse iterator)
  • 迭代器前进操作(operator++):
    • 其前进操作时首先尝试从目前所知的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易达到进行的目的
    • 如果目前节点正巧是list的尾端,就跳到下一个bucket内,跳过之后指向下一个list的头节点
    • 下面是operator++的定义

  
  1. template < class _Val, class _Key, class _HF, class _ExK, class _EqK,
  2. class _All>
  3. _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>&
  4. _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>:: operator++()
  5. {
  6. const _Node* __old = _M_cur;
  7. _M_cur = _M_cur->_M_next; //如果存在,就是它,否则进入if
  8. if (!_M_cur) {
  9. //根据元素值,定位出下一个bucket。其起头处就是我们的目的地
  10. size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val);
  11. while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size()) //注意operator++
  12. _M_cur = _M_ht->_M_buckets[__bucket];
  13. }
  14. return * this;
  15. }
  16. template < class _Val, class _Key, class _HF, class _ExK, class _EqK,
  17. class _All>
  18. inline _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>
  19. _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>:: operator++( int)
  20. {
  21. iterator __tmp = * this;
  22. ++* this; //调用operator++()
  23. return __tmp;
  24. }

四、hash table的数据结构(hashtable)

  • 下面就是哈希表的结构,其内部是以vector实现的
  • 模板参数比较多,包括:
    • _Val:节点的实值类型
    • _Key:节点的键值类型
    • _HF:hash function的函数类型
    • _Ex:从节点取出键值的方法(函数或仿函数)
    • _Eq:判断键值相同与否的方法(函数或仿函数)
    • _All:空间配置器。缺省使用std::alloc

  
  1. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  2. class hashtable;
  3. template < class _Val, class _Key, class _HashFcn,
  4. class _ ExtractKey, class _ EqualKey, class _ Alloc>
  5. class hashtable {
  6. public:
  7. typedef _Key key_type;
  8. typedef _Val value_type;
  9. typedef _HashFcn hasher; //为template类型参数重新定义一个名字
  10. typedef _EqualKey key_equal; //为template类型参数重新定义一个名字
  11. typedef size_t size_type;
  12. typedef ptrdiff_t difference_type;
  13. typedef value_type* pointer;
  14. typedef const value_type* const_pointer;
  15. typedef value_type& reference;
  16. typedef const value_type& const_reference;
  17. hasher hash_funct() const { return _M_hash; }
  18. key_equal key_eq() const { return _M_equals; }
  19. private:
  20. typedef _Hashtable_node<_Val> _Node;
  21. #ifdef __STL_USE_STD_ALLOCATORS
  22. public:
  23. typedef typename _Alloc_traits<_Val,_Alloc>::allocator_type allocator_type;
  24. allocator_type get_allocator() const { return _M_node_allocator; }
  25. private:
  26. typename _Alloc_traits<_Node, _Alloc>::allocator_type _M_node_allocator;
  27. _Node* _M_get_node() { return _M_node_allocator.allocate( 1); }
  28. void _M_put_node(_Node* __p) { _M_node_allocator.deallocate(__p, 1); }
  29. # define __HASH_ALLOC_INIT(__a) _M_node_allocator(__a),
  30. #else /* __STL_USE_STD_ALLOCATORS */
  31. public:
  32. typedef _Alloc allocator_type;
  33. allocator_type get_allocator() const { return allocator_type(); }
  34. private:
  35. typedef simple_alloc<_Node, _Alloc> _M_node_allocator_type;
  36. _Node* _M_get_node() { return _M_node_allocator_type::allocate( 1); }
  37. void _M_put_node(_Node* __p) { _M_node_allocator_type::deallocate(__p, 1); }
  38. # define __HASH_ALLOC_INIT(__a)
  39. #endif /* __STL_USE_STD_ALLOCATORS */
  40. private:
  41. hasher _M_hash;
  42. key_equal _M_equals;
  43. _ExtractKey _M_get_key;
  44. vector<_Node*,_Alloc> _M_buckets; //哈希表以vector实现
  45. size_type _M_num_elements;
  46. public:
  47. typedef _Hashtable_iterator<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc>
  48. iterator;
  49. typedef _Hashtable_const_iterator<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,
  50. _Alloc>
  51. const_iterator;
  52. friend struct
  53. _ Hashtable_iterator<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc>;
  54. friend struct
  55. _ Hashtable_const_iterator<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc>;
  56. public:
  57. hashtable(size_type __n,
  58. const _HashFcn& __hf,
  59. const _EqualKey& __eql,
  60. const _ExtractKey& __ext,
  61. const allocator_type& __a = allocator_type())
  62. : __HASH_ALLOC_INIT(__a)
  63. _M_hash(__hf),
  64. _M_equals(__eql),
  65. _M_get_key(__ext),
  66. _M_buckets(__a),
  67. _M_num_elements( 0)
  68. {
  69. _M_initialize_buckets(__n);
  70. }
  71. hashtable(size_type __n,
  72. const _HashFcn& __hf,
  73. const _EqualKey& __eql,
  74. const allocator_type& __a = allocator_type())
  75. : __HASH_ALLOC_INIT(__a)
  76. _M_hash(__hf),
  77. _M_equals(__eql),
  78. _M_get_key(_ExtractKey()),
  79. _M_buckets(__a),
  80. _M_num_elements( 0)
  81. {
  82. _M_initialize_buckets(__n);
  83. }
  84. hashtable( const hashtable& __ht)
  85. : __HASH_ALLOC_INIT(__ht.get_allocator())
  86. _M_hash(__ht._M_hash),
  87. _M_equals(__ht._M_equals),
  88. _M_get_key(__ht._M_get_key),
  89. _M_buckets(__ht.get_allocator()),
  90. _M_num_elements( 0)
  91. {
  92. _M_copy_from(__ht);
  93. }
  94. #undef __HASH_ALLOC_INIT
  95. hashtable& operator= ( const hashtable& __ht)
  96. {
  97. if (&__ht != this) {
  98. clear();
  99. _M_hash = __ht._M_hash;
  100. _M_equals = __ht._M_equals;
  101. _M_get_key = __ht._M_get_key;
  102. _M_copy_from(__ht);
  103. }
  104. return * this;
  105. }
  106. ~hashtable() { clear(); }
  107. size_type size() const { return _M_num_elements; }
  108. size_type max_size() const { return size_type( -1); }
  109. bool empty() const { return size() == 0; }
  110. void swap(hashtable& __ht)
  111. {
  112. __STD::swap(_M_hash, __ht._M_hash);
  113. __STD::swap(_M_equals, __ht._M_equals);
  114. __STD::swap(_M_get_key, __ht._M_get_key);
  115. _M_buckets.swap(__ht._M_buckets);
  116. __STD::swap(_M_num_elements, __ht._M_num_elements);
  117. }
  118. iterator begin()
  119. {
  120. for (size_type __n = 0; __n < _M_buckets.size(); ++__n)
  121. if (_M_buckets[__n])
  122. return iterator(_M_buckets[__n], this);
  123. return end();
  124. }
  125. iterator end() { return iterator( 0, this); }
  126. const_iterator begin() const
  127. {
  128. for (size_type __n = 0; __n < _M_buckets.size(); ++__n)
  129. if (_M_buckets[__n])
  130. return const_iterator(_M_buckets[__n], this);
  131. return end();
  132. }
  133. const_iterator end() const { return const_iterator( 0, this); }
  134. #ifdef __STL_MEMBER_TEMPLATES
  135. template < class _Vl, class _Ky, class _HF, class _Ex, class _Eq, class _Al>
  136. friend bool operator== ( const hashtable<_Vl, _Ky, _HF, _Ex, _Eq, _Al>&,
  137. const hashtable<_Vl, _Ky, _HF, _Ex, _Eq, _Al>&);
  138. #else /* __STL_MEMBER_TEMPLATES */
  139. friend bool __STD_QUALIFIER
  140. operator== __STL_NULL_TMPL_ARGS ( const hashtable&, const hashtable&);
  141. #endif /* __STL_MEMBER_TEMPLATES */
  142. public:
  143. //返回vector的大小
  144. size_type bucket_count() const { return _M_buckets.size(); }
  145. size_type max_bucket_count() const
  146. { return __stl_prime_list[( int)__stl_num_primes - 1]; }
  147. size_type elems_in_bucket(size_type __bucket) const
  148. {
  149. size_type __result = 0;
  150. for (_Node* __cur = _M_buckets[__bucket]; __cur; __cur = __cur->_M_next)
  151. __result += 1;
  152. return __result;
  153. }
  154. pair<iterator, bool> insert_unique( const value_type& __obj)
  155. {
  156. resize(_M_num_elements + 1);
  157. return insert_unique_noresize(__obj);
  158. }
  159. iterator insert_equal(const value_type& __obj)
  160. {
  161. resize(_M_num_elements + 1);
  162. return insert_equal_noresize(__obj);
  163. }
  164. pair<iterator, bool> insert_unique_noresize( const value_type& __obj);
  165. iterator insert_equal_noresize(const value_type& __obj);
  166. #ifdef __STL_MEMBER_TEMPLATES
  167. template < class _InputIterator>
  168. void insert_unique(_InputIterator __f, _InputIterator __l)
  169. {
  170. insert_unique(__f, __l, __ITERATOR_CATEGORY(__f));
  171. }
  172. template < class _InputIterator>
  173. void insert_equal(_InputIterator __f, _InputIterator __l)
  174. {
  175. insert_equal(__f, __l, __ITERATOR_CATEGORY(__f));
  176. }
  177. template < class _InputIterator>
  178. void insert_unique(_InputIterator __f, _InputIterator __l,
  179. input_iterator_tag)
  180. {
  181. for ( ; __f != __l; ++__f)
  182. insert_unique(*__f);
  183. }
  184. template < class _InputIterator>
  185. void insert_equal(_InputIterator __f, _InputIterator __l,
  186. input_iterator_tag)
  187. {
  188. for ( ; __f != __l; ++__f)
  189. insert_equal(*__f);
  190. }
  191. template < class _ForwardIterator>
  192. void insert_unique(_ForwardIterator __f, _ForwardIterator __l,
  193. forward_iterator_tag)
  194. {
  195. size_type __n = 0;
  196. distance(__f, __l, __n);
  197. resize(_M_num_elements + __n);
  198. for ( ; __n > 0; --__n, ++__f)
  199. insert_unique_noresize(*__f);
  200. }
  201. template < class _ForwardIterator>
  202. void insert_equal(_ForwardIterator __f, _ForwardIterator __l,
  203. forward_iterator_tag)
  204. {
  205. size_type __n = 0;
  206. distance(__f, __l, __n);
  207. resize(_M_num_elements + __n);
  208. for ( ; __n > 0; --__n, ++__f)
  209. insert_equal_noresize(*__f);
  210. }
  211. #else /* __STL_MEMBER_TEMPLATES */
  212. void insert_unique(const value_type* __f, const value_type* __l)
  213. {
  214. size_type __n = __l - __f;
  215. resize(_M_num_elements + __n);
  216. for ( ; __n > 0; --__n, ++__f)
  217. insert_unique_noresize(*__f);
  218. }
  219. void insert_equal(const value_type* __f, const value_type* __l)
  220. {
  221. size_type __n = __l - __f;
  222. resize(_M_num_elements + __n);
  223. for ( ; __n > 0; --__n, ++__f)
  224. insert_equal_noresize(*__f);
  225. }
  226. void insert_unique(const_iterator __f, const_iterator __l)
  227. {
  228. size_type __n = 0;
  229. distance(__f, __l, __n);
  230. resize(_M_num_elements + __n);
  231. for ( ; __n > 0; --__n, ++__f)
  232. insert_unique_noresize(*__f);
  233. }
  234. void insert_equal(const_iterator __f, const_iterator __l)
  235. {
  236. size_type __n = 0;
  237. distance(__f, __l, __n);
  238. resize(_M_num_elements + __n);
  239. for ( ; __n > 0; --__n, ++__f)
  240. insert_equal_noresize(*__f);
  241. }
  242. #endif /*__STL_MEMBER_TEMPLATES */
  243. reference find_or_insert(const value_type& __obj);
  244. iterator find(const key_type& __key)
  245. {
  246. size_type __n = _M_bkt_num_key(__key);
  247. _Node* __first;
  248. for ( __first = _M_buckets[__n];
  249. __first && !_M_equals(_M_get_key(__first->_M_val), __key);
  250. __first = __first->_M_next)
  251. {}
  252. return iterator(__first, this);
  253. }
  254. const_iterator find(const key_type& __key) const
  255. {
  256. size_type __n = _M_bkt_num_key(__key);
  257. const _Node* __first;
  258. for ( __first = _M_buckets[__n];
  259. __first && !_M_equals(_M_get_key(__first->_M_val), __key);
  260. __first = __first->_M_next)
  261. {}
  262. return const_iterator(__first, this);
  263. }
  264. size_type count(const key_type& __key) const
  265. {
  266. const size_type __n = _M_bkt_num_key(__key);
  267. size_type __result = 0;
  268. for ( const _Node* __cur = _M_buckets[__n]; __cur; __cur = __cur->_M_next)
  269. if (_M_equals(_M_get_key(__cur->_M_val), __key))
  270. ++__result;
  271. return __result;
  272. }
  273. pair<iterator, iterator>
  274. equal_range( const key_type& __key);
  275. pair<const_iterator, const_iterator>
  276. equal_range( const key_type& __key) const;
  277. size_type erase(const key_type& __key);
  278. void erase(const iterator& __it);
  279. void erase(iterator __first, iterator __last);
  280. void erase(const const_iterator& __it);
  281. void erase(const_iterator __first, const_iterator __last);
  282. void resize(size_type __num_elements_hint);
  283. void clear();
  284. private:
  285. size_type _M_next_size(size_type __n) const
  286. { return __stl_next_prime(__n); }
  287. void _M_initialize_buckets(size_type __n)
  288. {
  289. const size_type __n_buckets = _M_next_size(__n);
  290. _M_buckets.reserve(__n_buckets);
  291. _M_buckets.insert(_M_buckets.end(), __n_buckets, (_Node*) 0);
  292. _M_num_elements = 0;
  293. }
  294. size_type _M_bkt_num_key( const key_type& __key) const
  295. {
  296. return _M_bkt_num_key(__key, _M_buckets.size());
  297. }
  298. size_type _M_bkt_num( const value_type& __obj) const
  299. {
  300. return _M_bkt_num_key(_M_get_key(__obj));
  301. }
  302. size_type _M_bkt_num_key( const key_type& __key, size_t __n) const
  303. {
  304. return _M_hash(__key) % __n;
  305. }
  306. size_type _M_bkt_num( const value_type& __obj, size_t __n) const
  307. {
  308. return _M_bkt_num_key(_M_get_key(__obj), __n);
  309. }
  310. _Node* _M_new_node( const value_type& __obj)
  311. {
  312. _Node* __n = _M_get_node();
  313. __n->_M_next = 0;
  314. __STL_TRY {
  315. construct(&__n->_M_val, __obj);
  316. return __n;
  317. }
  318. __STL_UNWIND(_M_put_node(__n));
  319. }
  320. void _M_delete_node(_Node* __n)
  321. {
  322. destroy(&__n->_M_val);
  323. _M_put_node(__n);
  324. }
  325. void _M_erase_bucket( const size_type __n, _Node* __first, _Node* __last);
  326. void _M_erase_bucket( const size_type __n, _Node* __last);
  327. void _M_copy_from( const hashtable& __ht);
  328. };

系统预定义的哈希大小

  • 虽然链地址法并不要求哈希表大小必须为质数,但SGI STL仍然以质数来设计哈希表大小,并且先将28个质数(主键呈现大约2倍的关系)计算好,以备随时使用,同时提供一个函数,用来查询在这28个质数之中,“最接某数并大于某数”的质数

   
  1. //下面的都是全局枚举与全局函数
  2. //假设long至少有32bits
  3. enum { __stl_num_primes = 28 };
  4. static const unsigned long __stl_prime_list[__stl_num_primes] =
  5. {
  6. 53ul, 97ul, 193ul, 389ul, 769ul,
  7. 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
  8. 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
  9. 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
  10. 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
  11. 1610612741ul, 3221225473ul, 4294967291ul
  12. };
  13. //以下找出上述28个质数之中,最接近并大于n的那个质数
  14. inline unsigned long __stl_next_prime( unsigned long __n)
  15. {
  16. const unsigned long* __first = __stl_prime_list;
  17. const unsigned long* __last = __stl_prime_list + ( int)__stl_num_primes;
  18. const unsigned long* pos = lower_bound(__first, __last, __n);
  19. //以上lower_bound()是泛型算法
  20. //使用lower_bound(),序列需先排序
  21. return pos == __last ? *(__last - 1) : *pos;
  22. }

   
  1. template < class _Val, class _Key, class _HashFcn,
  2. class _ ExtractKey, class _ EqualKey, class _ Alloc>
  3. class hashtable {
  4. //...
  5. public:
  6. //总共可以有多少buckets
  7. size_type max_bucket_count() const
  8. { return __stl_prime_list[( int)__stl_num_primes - 1]; }
  9. //...
  10. };

五、hash table的构造与内存管理

  • 在hashtable类中有一个专属的节点配置器_M_node_allocator_type

  
  1. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  2. class hashtable;
  3. template < class _Val, class _Key, class _HashFcn,
  4. class _ ExtractKey, class _ EqualKey, class _ Alloc>
  5. class hashtable {
  6. //...
  7. private:
  8. typedef simple_alloc<_Node, _Alloc> _M_node_allocator_type;
  9. //...
  10. };
  • 下面是定义在hashtable类中的节点的配置函数与释放函数

  
  1. _Node* _M_new_node( const value_type& __obj)
  2. {
  3. _Node* __n = _M_get_node();
  4. __n->_M_next = 0;
  5. __STL_TRY {
  6. construct(&__n->_M_val, __obj);
  7. return __n;
  8. }
  9. __STL_UNWIND(_M_put_node(__n));
  10. }
  11. void _M_delete_node(_Node* __n)
  12. {
  13. destroy(&__n->_M_val);
  14. _M_put_node(__n);
  15. }
  16. void _M_put_node(_Node* __p) { _M_node_allocator.deallocate(__p, 1); }

构造函数

  • hashtable没有提供默认的构造函数。下面是一种构造函数

   
  1. template < class _Val, class _Key, class _HashFcn,
  2. class _ ExtractKey, class _ EqualKey, class _ Alloc>
  3. class hashtable {
  4. public:
  5. hashtable(size_type __n,
  6. const _HashFcn& __hf,
  7. const _EqualKey& __eql,
  8. const _ExtractKey& __ext,
  9. const allocator_type& __a = allocator_type())
  10. : __HASH_ALLOC_INIT(__a)
  11. _M_hash(__hf),
  12. _M_equals(__eql),
  13. _M_get_key(__ext),
  14. _M_buckets(__a),
  15. _M_num_elements( 0)
  16. {
  17. _M_initialize_buckets(__n);
  18. }
  19. private:
  20. void _M_initialize_buckets(size_type __n)
  21. {
  22. const size_type __n_buckets = _M_next_size(__n); //调用_M_next_size
  23. //举例:传入50,返回53.以下首先保留53个元素空间,然后将其全部填0
  24. _M_buckets.reserve(__n_buckets);
  25. _M_buckets.insert(_M_buckets.end(), __n_buckets, (_Node*) 0);
  26. _M_num_elements = 0;
  27. }
  28. //该函数返回最近接n并大于n的质数。其中调用了我们上面介绍的__stl_next_prime函数
  29. size_type _M_next_size(size_type __n) const
  30. { return __stl_next_prime(__n); }
  31. };
  • 例如下面我们定义一个hashtable就会调用上面的构造函数

   
  1. hashtable< int, int,hash<iny>,identity< int>,equal_to< int>,alloc> iht( 50,hash< int>(),equal_to< int>());
  2. std:: cout<<iht.size()<< std:: endl; //0
  3. std:: cout<<iht.bucket_count()<< std:: endl; //53。STL提供的第一个质数

插入操作(insert_unique)和表格重整(resize)

  • insert_unique():插入元素,不允许重复

   
  1. pair<iterator, bool> insert_unique( const value_type& __obj)
  2. {
  3. resize(_M_num_elements + 1); //判断是否需要重建表格
  4. return insert_unique_noresize(__obj); //在不需要重建表格的情况下插入节点,键值不允许重复
  5. }
  • resize()函数:

   
  1. //以下函数判断是否需要重建表格。不需要就返回
  2. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  3. void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  4. ::resize(size_type __num_elements_hint)
  5. {
  6. //下面是否重建表格的判断原则很奇怪,是拿元素个数(把新增元素计入后)和bucket vector的大小来比较
  7. //如果前者大于后者,就重建表格
  8. //由此可知,每个bucket(list)的最大容量和buckets vector的大小相同
  9. const size_type __old_n = _M_buckets.size();
  10. if (__num_elements_hint > __old_n) { //确定真的需要重新配置
  11. const size_type __n = _M_next_size(__num_elements_hint); //找出下一个质数
  12. if (__n > __old_n) {
  13. vector<_Node*, _All> __tmp(__n, (_Node*)( 0),
  14. _M_buckets.get_allocator()); //建立新的buckets
  15. __STL_TRY {
  16. //以下处理每一个旧的bucket
  17. for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
  18. _Node* __first = _M_buckets[__bucket];
  19. while (__first) {
  20. size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);
  21. _M_buckets[__bucket] = __first->_M_next;
  22. __first->_M_next = __tmp[__new_bucket];
  23. __tmp[__new_bucket] = __first;
  24. __first = _M_buckets[__bucket];
  25. }
  26. }
  27. _M_buckets.swap(__tmp);
  28. }
  29. # ifdef __STL_USE_EXCEPTIONS
  30. catch(...) {
  31. for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
  32. while (__tmp[__bucket]) {
  33. _Node* __next = __tmp[__bucket]->_M_next;
  34. _M_delete_node(__tmp[__bucket]);
  35. __tmp[__bucket] = __next;
  36. }
  37. }
  38. throw;
  39. }
  40. # endif /* __STL_USE_EXCEPTIONS */
  41. }
  42. }
  43. }
  • 在上面的resize()函数中,如果有必要就做表格重建。操作分解如下:

   
  1. _M_buckets[__bucket] = __first->_M_next; //(1)令旧的bucket指向其所对应之链表的下一个节点
  2. __first->_M_next = __tmp[__new_bucket]; //(2)(3)将当前节点插入到新bucket内,成为其对应链表的第一个节点
  3. __tmp[__new_bucket] = __first; //(4)回到旧的bucket所指的待处理链表,准备处理下一个节点

 


   
  1. //在不需要重建表格的情况下插入节点,键值不允许重复
  2. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  3. pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator, bool>
  4. hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  5. ::insert_unique_noresize( const value_type& __obj)
  6. {
  7. const size_type __n = _M_bkt_num(__obj); //决定obj应位于#n bucket
  8. _Node* __first = _M_buckets[__n]; //令first指向bucket对应之串行头部
  9. //如果buckets[n]已被占用,此时first将不为0,于是进入以下循环,走过bucket所对应的整个链表
  10. for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
  11. if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))
  12. //如果发现与链表中的某键值相同,就不插入,立即返回
  13. return pair<iterator, bool>(iterator(__cur, this), false);
  14. //离开循环(或根本不进入循环)时,first指向bucket所指链表的头结点
  15. _Node* __tmp = _M_new_node(__obj);
  16. __tmp->_M_next = __first;
  17. _M_buckets[__n] = __tmp; //令新节点称为链表的第一个节点
  18. ++_M_num_elements; //节点个数累加1
  19. return pair<iterator, bool>(iterator(__tmp, this), true);
  20. }

插入操作(insert_equal


   
  1. iterator insert_equal(const value_type& __obj)
  2. {
  3. resize(_M_num_elements + 1); //在上面已介绍
  4. return insert_equal_noresize(__obj);
  5. }

   
  1. //在不需要重建表格的情况下插入新节点,键值允许重复
  2. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  3. typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator
  4. hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  5. ::insert_equal_noresize( const value_type& __obj)
  6. {
  7. const size_type __n = _M_bkt_num(__obj);
  8. _Node* __first = _M_buckets[__n];
  9. for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
  10. if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj))) {
  11. _Node* __tmp = _M_new_node(__obj);
  12. __tmp->_M_next = __cur->_M_next;
  13. __cur->_M_next = __tmp;
  14. ++_M_num_elements;
  15. return iterator(__tmp, this);
  16. }
  17. _Node* __tmp = _M_new_node(__obj);
  18. __tmp->_M_next = __first;
  19. _M_buckets[__n] = __tmp;
  20. ++_M_num_elements;
  21. return iterator(__tmp, this);
  22. }

判断元素落脚点(bkt_num)

  • 插入元素之后需要知道某个元素落脚于哪一个bucket内。这本来是哈希函数的责任,但是SGI把这个任务包装了一层,先交给bkt_num()函数,再由此函数调用哈希函数,取得一个可以执行modulus(取模)运算的数值
  • 为什么要这么做?因为有些函数类型无法直接拿来对哈表表的大小进行模运算,例如字符串,这时候我们需要做一些转换
  • 下面是bkt_num函数:

   
  1. //版本1:接受实值(value)和buckets个数
  2. size_type _M_bkt_num( const value_type& __obj, size_t __n) const
  3. {
  4. return _M_bkt_num_key(_M_get_key(__obj), __n); //调用版本4
  5. }
  6. //版本2:只接受实值(value)
  7. size_type _M_bkt_num( const value_type& __obj) const
  8. {
  9. return _M_bkt_num_key(_M_get_key(__obj)); //调用版本3
  10. }
  11. //版本3,只接受键值
  12. size_type _M_bkt_num_key( const key_type& __key) const
  13. {
  14. return _M_bkt_num_key(__key, _M_buckets.size()); //调用版本4
  15. }
  16. //版本4:接受键值和buckets个数
  17. size_type _M_bkt_num_key( const key_type& __key, size_t __n) const
  18. {
  19. return _M_hash(__key) % __n; //SGI的所有内建的hash(),在后面的hash functions中介绍
  20. }

复制(copy_from)和整体删除(clear)

  • 由于整个哈希表由vector和链表组成,因此,复制和整体删除,都需要特别注意内存的释放问题
  • 下面是clear()的定义:

   
  1. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  2. void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::clear()
  3. {
  4. //针对每一个bucket
  5. for (size_type __i = 0; __i < _M_buckets.size(); ++__i) {
  6. _Node* __cur = _M_buckets[__i];
  7. //将bucket list中的每一个节点删除
  8. while (__cur != 0) {
  9. _Node* __next = __cur->_M_next;
  10. _M_delete_node(__cur);
  11. __cur = __next;
  12. }
  13. _M_buckets[__i] = 0; //令bucket内容为null指针
  14. }
  15. _M_num_elements = 0; //令总节点个数为0
  16. //注意,buckets vector并未释放迪奥空间,仍然保有原来大小
  17. }
  • 下面是copy_from()的定义: 

   
  1. template < class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
  2. void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
  3. ::_M_copy_from( const hashtable& __ht)
  4. {
  5. _M_buckets.clear(); //先清除自己的bucket vector
  6. //为自己的buvkets vector保留空间,使与对方相同
  7. //如果自己空间大于对方就不动。如果自己空间小于对方,就增大
  8. _M_buckets.reserve(__ht._M_buckets.size());
  9. //从自己的buckets vector尾端开始,插入n个元素,其值为null
  10. //注意,此时buckets vector为空,所以所谓尾端,就是起头处
  11. _M_buckets.insert(_M_buckets.end(), __ht._M_buckets.size(), (_Node*) 0);
  12. __STL_TRY {
  13. //针对buckets vector
  14. for (size_type __i = 0; __i < __ht._M_buckets.size(); ++__i) {
  15. //复制vector的每一个元素(是个指针,指向节点)
  16. const _Node* __cur = __ht._M_buckets[__i];
  17. if (__cur) {
  18. _Node* __copy = _M_new_node(__cur->_M_val);
  19. _M_buckets[__i] = __copy;
  20. //针对同一个bucket list,复制每一个节点
  21. for (_Node* __next = __cur->_M_next;
  22. __next;
  23. __cur = __next, __next = __cur->_M_next) {
  24. __copy->_M_next = _M_new_node(__next->_M_val);
  25. __copy = __copy->_M_next;
  26. }
  27. }
  28. }
  29. _M_num_elements = __ht._M_num_elements; //重新记录节点个数(哈希表的大小)
  30. }
  31. __STL_UNWIND(clear());
  32. }

六、hash functions

  • <stl_hash_fun.h>中定义了一些hash functions,全都是仿函数
  • hash functions是计算元素位置的函数
  • SGI将这项任务赋予了先前提到过的bkt_num()函数,再由bkt_num()函数调用这些hash function,取得一个可以对hashtable进行模运算的值
  • 针对char、int、long等整数类型,这里大部分的hash function什么都没有做,只是直接返回原值。但是对于字符串类型,就设计了一个转换函数如下:

  
  1. template < class _Key> struct hash { };
  2. inline size_t __stl_hash_string( const char* __s)
  3. {
  4. unsigned long __h = 0;
  5. for ( ; *__s; ++__s)
  6. __h = 5*__h + *__s;
  7. return size_t(__h);
  8. }
  9. //以下所有的__STL_TEMPLATE_NULL在<stl_config.h>中被定义为template<>
  10. __STL_TEMPLATE_NULL struct hash<char*>
  11. {
  12. size_t operator()( const char* __s) const { return __stl_hash_string(__s); }
  13. };
  14. __STL_TEMPLATE_NULL struct hash<const char*>
  15. {
  16. size_t operator()( const char* __s) const { return __stl_hash_string(__s); }
  17. };
  18. __STL_TEMPLATE_NULL struct hash<char> {
  19. size_t operator()( char __x) const { return __x; }
  20. };
  21. __STL_TEMPLATE_NULL struct hash<unsigned char> {
  22. size_t operator()( unsigned char __x) const { return __x; }
  23. };
  24. __STL_TEMPLATE_NULL struct hash<signed char> {
  25. size_t operator()( unsigned char __x) const { return __x; }
  26. };
  27. __STL_TEMPLATE_NULL struct hash<short> {
  28. size_t operator()( short __x) const { return __x; }
  29. };
  30. __STL_TEMPLATE_NULL struct hash<unsigned short> {
  31. size_t operator()( unsigned short __x) const { return __x; }
  32. };
  33. __STL_TEMPLATE_NULL struct hash<int> {
  34. size_t operator()( int __x) const { return __x; }
  35. };
  36. __STL_TEMPLATE_NULL struct hash<unsigned int> {
  37. size_t operator()( unsigned int __x) const { return __x; }
  38. };
  39. __STL_TEMPLATE_NULL struct hash<long> {
  40. size_t operator()( long __x) const { return __x; }
  41. };
  42. __STL_TEMPLATE_NULL struct hash<unsigned long> {
  43. size_t operator()( unsigned long __x) const { return __x; }
  44. };

hash functions无法处理的类型

  • hashtable无法处理上述所列各项类型之外的元素。例如string、double、float等,这些类型用户必须自己定义hash function
  • 下面是处理string所获的错误现象

七、演示案例

  • 下面是一个客户端程序

  
  1. #include <hash_set> //会包含<stl_hashtable.h>
  2. #include <iostream>
  3. using namespace std;
  4. int main()
  5. {
  6. hashtable< int, int, hash< int>, identity< int>, equal_to< int>, alloc>
  7. iht( 50, hash< int>(), equal_to< int>());
  8. std:: cout << iht.size() << std:: endl; //0
  9. std:: cout << iht.bucket_count()() << std:: endl; //53。这是STL供应的第一个质数
  10. std:: cout << iht.max_bucket_count() << std:: endl; //4294967291,这是STL供应的最后一个质数
  11. iht.insert_unique( 59);
  12. iht.insert_unique( 63);
  13. iht.insert_unique( 108);
  14. iht.insert_unique( 2);
  15. iht.insert_unique( 53);
  16. iht.insert_unique( 55);
  17. std:: cout << iht.size() << std:: endl; //6。这个就是hashtable<T>::num_element
  18. //下面声明一个hashtable迭代器
  19. hashtable< int, int, hash< int>, identity< int>, equal_to< int>, alloc>::iterator
  20. ite = iht.begin();
  21. //遍历hashtable
  22. for ( int i = 0; i < iht.size(); ++i, ++ite)
  23. std:: cout << *ite << std:: endl; //53 55 2 108 59 53
  24. std:: cout << std:: endl;
  25. //遍历所有buckets。如果其节点个数不为0,就打印节点个数
  26. for ( int i = 0; i < iht.bucket_count(); ++i) {
  27. int n = iht.elems_in_bucket(i);
  28. if (n != 0)
  29. std:: cout << "bucket[" << i << "]has"<<n<< "elems." << std:: endl;
  30. }
  31. //会打印如下内容
  32. //bucket[0] has 1 elems
  33. //bucket[2] has 3 elems
  34. //bucket[6] has 1 elems
  35. //bucket[10] has 1 elems
  36. /*为了验证bucket(list)的容量就是buckets vector的大小,
  37. 这里从hastable<T>::Resize()得到结果。此处刻意将元素加到54个,
  38. 看看是否发生”表格重建“
  39. */
  40. for ( int i = 0; i <= 47; ++i)
  41. iht.insert_equal(i);
  42. std:: cout << iht.size() << std:: endl; //54。元素(节点)个数
  43. std:: cout << iht.bucket_count() << std:: endl; //97,buckets个数
  44. //遍历所有buckets,如果其节点个数不为0,就打印节点个数
  45. for ( int i = 0; i < iht.bucket_count(); ++i) {
  46. int n = iht.elems_in_bucket(i);
  47. if (n != 0)
  48. std:: cout << "bucket[" << i << "]has" << n << "elems." << std:: endl;
  49. }
  50. //打印的结果为:bucket[2]和bucket[11]的节点个数为2
  51. //其余的bucket[0]~bucket[47]的节点个数为1
  52. //此外,bucket[53],[55],[59],[63]的节点个数均为1
  53. //以迭代器遍历hashtable,将所有节点的值打印出来
  54. ite = iht.begin();
  55. for ( int i = 0; i < iht.size(); ++i, ++ite)
  56. std:: cout << *ite << " ";
  57. std:: cout << std:: endl;
  58. //0 1 2 2 3 4 5 6 7 8 9 10 11 108 12 13 14 15 16 17 18 19 20 21
  59. //22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
  60. //43 44 45 46 47 53 55 59 63
  61. std:: cout << *(iht.find( 2)) << std:: endl; //2
  62. std:: cout << *(iht.count( 2)) << std:: endl; //2
  63. return 0;
  64. }
  • 一开始保留了50个节点,由于最接近的STL质数为53,所以buckets vector保留的是53个buckets,每个buckets(指针,指向一个hashtable节点)的初值为0
  • 接下来,循环加入6个元素:53、55、2、108、59、63。于是hash table变为下面的样子

  • 接下来,再插入48个元素,总元素达到54个,超过当时的buckets vector的大小,哈希表需要重建,于是哈希表变为下面的样子

  • 程序最后使用了hash table提供的find和count函数,寻找键值为2的元素,以及计算键值为2的元素个数。注意:键值相同的元素,一定落在同一个bucket list中。下面是find和count的源码

  
  1. iterator find(const key_type& __key)
  2. {
  3. size_type __n = _M_bkt_num_key(__key); //查看在哪一个bucket内
  4. _Node* __first;
  5. //下面,从bucket list的头开始,一一对比每个元素的键值,对比成功就退出
  6. for ( __first = _M_buckets[__n];
  7. __first && !_M_equals(_M_get_key(__first->_M_val), __key);
  8. __first = __first->_M_next)
  9. {}
  10. return iterator(__first, this);
  11. }

  
  1. size_type count(const key_type& __key) const
  2. {
  3. const size_type __n = _M_bkt_num_key(__key); //查看在哪一个bucket内
  4. size_type __result = 0;
  5. //下面,从bucket list的头开始,一一对比每个元素的键值,对比成功就累加1
  6. for ( const _Node* __cur = _M_buckets[__n]; __cur; __cur = __cur->_M_next)
  7. if (_M_equals(_M_get_key(__cur->_M_val), __key))
  8. ++__result;
  9. return __result;
  10. }

 


转载:https://blog.csdn.net/qq_41453285/article/details/104260053
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场