写在前面
(本专栏仅是个人笔记本,有胡言乱语和错漏部分)
本文以图文+代码的形式记录了_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_count
bucket的数量,若采用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决定是否const
345
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 set
89 {
90 template<typename _Tp>
91 _Tp&&
92 operator()(_Tp&& __x) const
93 { return std::forward<_Tp>(__x); }
94 };
95
96 struct _Select1st // for map
97 {
98 template<typename _Tp>
99 auto
100 operator()(_Tp&& __x) const
101 -> 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的哨兵节点。
用图描绘一下就是:
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_type
716 }
然后根据 __unique_keys(),调用不同重载版本的_M_insert():
//for multiset/multimap
687 template<typename _Arg, typename _NodeGenerator>
688 iterator
689 _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/set
1683 // 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 auto
1690 _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 auto
1420 _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) const
1424 -> __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的结构特殊,每次都要判断是否已经遍历完了当前的bucket
1436 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 auto
1586 _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 -> iterator
1591 {
//交由_RehashPolicy 判断 是否需要rehash,
1592 const __rehash_state& __saved_state = _M_rehash_policy._M_state();
1593 std::pair<bool, std::size_t> __do_rehash
1594 = _M_rehash_policy._M_need_rehash(_M_bucket_count, _M_element_count, 1); //1 表示要插入一个元素
1595
1596 __try
1597 {
1598 if (__do_rehash.first) //需要rehash
1599 {
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 void
1448 _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 node
1455 // after the bucket before begin.
1456 __node->_M_nxt = _M_buckets[__bkt]->_M_nxt;
1457 _M_buckets[__bkt]->_M_nxt = __node;
1458 }
1459 else
1460 {
1461 // The bucket is empty, the new node is inserted at the
1462 // beginning of the singly-linked list and the bucket will
1463 // 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 to
1468 // _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() const
370 { return static_cast<__node_type*>(_M_before_begin._M_nxt); } //返回哨兵节点的next指针
481 iterator
482 begin() noexcept
483 { return iterator(_M_begin()); }
489 iterator
490 end() noexcept
491 { return iterator(nullptr); }
505 size_type
506 size() const noexcept
507 { return _M_element_count; }
其他成员函数
只要理解了Hashtable的结构,其他成员函数的代码还是比较self-explained的,这里就暂且略过。