libc++ hashtable 源码简析
2016-01-31 22:55
603 查看
libc++ hashtable 源码简析
本文分析的是 https://github.com/llvm-mirror/libcxx/ 中截止至 2016 年 1 月 30 日最新的libc++。
libc++中,
hashtable的实现为链式结构。 在教科书(
Introduction To Algorithm 3rd Edition)中,介绍的实现是由一个数组作为
buckets,每个数组中存储一个链表。但是
libc++中,使用一个单向链表贯穿整个
hashtable,每个
slot存储的是上一个元素结点的指针,这个元素充当当前链表的头结点。也就是说,每个
slot的链表存储的实际上是一个左开右闭的区间。在
libc++中,实现链表普遍使用了一个技巧:有一个基类
xxx_node_base,这个里面仅存储指向下一个(或者还有上一个)结点的指针,而真正的
xxx_node里面才有元素。选用
xxx_node_base作为头结点,这样就减少了单个元素的内存占用。在
hashtable中,可以找到:
[code]template <class _NodePtr> struct __hash_node_base { typedef __hash_node_base __first_node; _NodePtr __next_; _LIBCPP_INLINE_VISIBILITY __hash_node_base() _NOEXCEPT : __next_(nullptr) {} }; template <class _Tp, class _VoidPtr> struct __hash_node : public __hash_node_base < typename __rebind_pointer<_VoidPtr, __hash_node<_Tp, _VoidPtr> >::type > { typedef _Tp value_type; size_t __hash_; value_type __value_; };
以下是
hashtable的关键成员变量:
[code]typedef unique_ptr<__node_pointer[], __bucket_list_deleter> __bucket_list; // --- Member data begin --- __bucket_list __bucket_list_; __compressed_pair<__first_node, __node_allocator> __p1_; __compressed_pair<size_type, hasher> __p2_; __compressed_pair<float, key_equal> __p3_; // --- Member data end ---
可以看到:
1.
buckets是一个
node_pointer的数组;
2.
__p1_.first()就是上文中说的没有元素的头结点;
3.
__p2_.first()是现在的元素个数;
4.
__p3_.first()是现在的
load factor。
在下文的代码中为了便于理解,
__p1_first()替换为
head_node。
看过
rehash函数后,就会对整个
hashtable有个大致的印象。其大致思路为:若当前结点(
cp或许指的是
current pointer?)与前一个结点的哈希值相同则不用理会——让它跟着前一个结点走好了。否则就说明我们找到了边界,那么就在这个
hashtable的链表最前面插入当前结点。这时候分两种情况。第一种是那个
slot是空的,那么就把当前结点对应的新
slot更新为整个链表的头结点,把原来以整个链表的头结点为局部链表头结点的
slot更新为这个结点的指针(这样就形成了开区间),第二种是非空,那么直接插入就好了。但是这里有个小优化:从
current_node开始,把所有相等元素的结点同时插入进去,由于是链表,这个操作可以
O(1)完成!另外,这个
hashtable会把每个元素的
hash value都缓存下来,这样在
rehash的时候无需再次
hash:
[code]template <class _Tp, class _Hash, class _Equal, class _Alloc> void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__rehash(size_type __nbc) { #if _LIBCPP_DEBUG_LEVEL >= 2 __get_db()->__invalidate_all(this); //在 rehash 后,需要 invalidate 所有相关 iterator #endif // _LIBCPP_DEBUG_LEVEL >= 2 __pointer_allocator& __npa = __bucket_list_.get_deleter().__alloc(); __bucket_list_.reset(__nbc > 0 ? __pointer_alloc_traits::allocate(__npa, __nbc) : nullptr); //分配新的 buckets __bucket_list_.get_deleter().size() = __nbc; if (__nbc > 0) { for (size_type __i = 0; __i < __nbc; ++__i) __bucket_list_[__i] = nullptr; //先将每个 slot 置为 nullptr __node_pointer previous_node(static_cast<__node_pointer>(pointer_traits<__node_base_pointer>::pointer_to(head_node))); //从头结点开始遍历 __node_pointer current_node = previous_node->__next_; if (current_node != nullptr) { //这是刚开始,需要特殊对待:先将当前结点(即紧挨着头结点的第一个结点)的 slot 更新 size_type __chash = __constrain_hash(current_node->__hash_, __nbc); __bucket_list_[__chash] = previous_node; size_type __phash = __chash; for (previous_node = current_node, current_node = current_node->__next_; current_node != nullptr; current_node = previous_node->__next_) { __chash = __constrain_hash(current_node->__hash_, __nbc); if (__chash == __phash) //如果当前结点的新哈希值和上一个一样,那么就跟着上一个走好了(上一个结点已经更新就位) previous_node = current_node; else { if (__bucket_list_[__chash] == nullptr) { //slot 里面什么都没有,那么根据上文说的“左开右闭”,把局部链表的头结点设置为上一个结点 __bucket_list_[__chash] = previous_node; previous_node = current_node; __phash = __chash; } else { //如果非空,那么一并将元素相等的一串[current_node, new_node]插入进去,这个操作可以瞬间完成! __node_pointer new_node = current_node; for (; new_node->__next_ != nullptr && key_eq()(current_node->__value_, new_node->__next_->__value_); new_node = new_node->__next_) ; previous_node->__next_ = new_node->__next_; new_node->__next_ = __bucket_list_[__chash]->__next_; __bucket_list_[__chash]->__next_ = current_node; } } } } } }
了解了
rehash的思路,那么
insert也就十分明了了:如果插入后的新
load factor比
max_load_factor大了,就
rehash。(由于这个是
insert_unique,所以一旦发现重复的就不管了) 如果待插入的
slot是空,那么把新结点插入到最前面,然后整个链表的头结点设置为这个
slot的头结点,然后将原先以
head_node为头结点的局部链表的头结点更新为这个新插入的结点(这个新插入的结点在它们前面,实现了左开右闭),如果
slot非空,就直接插入好了:
[code]template <class _Tp, class _Hash, class _Equal, class _Alloc> pair<typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator, bool> __hash_table<_Tp, _Hash, _Equal, _Alloc>::__node_insert_unique(__node_pointer __nd) { __nd->__hash_ = hash_function()(__nd->__value_); size_type __bc = bucket_count(); bool __inserted = false; __node_pointer __ndptr; size_t __chash; if (__bc != 0) { __chash = __constrain_hash(__nd->__hash_, __bc); __ndptr = __bucket_list_[__chash]; if (__ndptr != nullptr) { //通过比较当前结点值的哈希值与要插入结点的哈希值是否相等来找到边界,老把戏了。 for (__ndptr = __ndptr->__next_; __ndptr != nullptr && __constrain_hash(__ndptr->__hash_, __bc) == __chash; __ndptr = __ndptr->__next_) { if (key_eq()(__ndptr->__value_, __nd->__value_)) goto __done; } } } { if (size()+1 > __bc * max_load_factor() || __bc == 0) { rehash(_VSTD::max<size_type>(2 * __bc + !__is_hash_power2(__bc), size_type(ceil(float(size() + 1) / max_load_factor())))); __bc = bucket_count(); __chash = __constrain_hash(__nd->__hash_, __bc); } // insert_after __bucket_list_[__chash], or __first_node if bucket is null __node_pointer __pn = __bucket_list_[__chash]; if (__pn == nullptr) { __pn = static_cast<__node_pointer>(pointer_traits<__node_base_pointer>::pointer_to(head_node)); __nd->__next_ = __pn->__next_; __pn->__next_ = __nd; // fix up __bucket_list_ __bucket_list_[__chash] = __pn; if (__nd->__next_ != nullptr) __bucket_list_[__constrain_hash(__nd->__next_->__hash_, __bc)] = __nd; } else { __nd->__next_ = __pn->__next_; __pn->__next_ = __nd; } __ndptr = __nd; // increment size ++size(); __inserted = true; } __done: #if _LIBCPP_DEBUG_LEVEL >= 2 return pair<iterator, bool>(iterator(__ndptr, this), __inserted); #else return pair<iterator, bool>(iterator(__ndptr), __inserted); #endif }
相关文章推荐
- 值得推荐的C/C++框架和库
- 一起talk C栗子吧(第一百一十六回:C语言实例--线程同步之互斥量二)
- c++下基于windows socket的服务器客户端程序(基于UDP协议)
- 第三届蓝桥杯C/C++组第十题 取球游戏(博弈)
- 紫书第5章 C++STL
- C++builder 6的安装说明
- c语言:请编程序将“China”译成密码,分别用putchar和printf函数输出这5个字符。
- C++,笔试面试,使用C++编程,实现万年历
- c语言:用scanf函数输入数据,举例并分析错误原因
- 【干货】国外程序员整理的 C++ 资源大全
- C++学习笔记(六) 面向对象
- 九度oj 二叉排序树 1201,1009 c++
- 寒假短期学习计划 - C++
- 第三届蓝桥杯C/C++组第九题 足球比赛(概率论+随机数)
- C++ MOOC
- Effective C++ 24,25
- C++ 解析Json——jsoncpp
- 数独解法 C++实现
- c++ bitset类的使用和简介
- C++全局变量