http://www.cnblogs.com/JackyTecblog/archive/2012/08/12/2634764.html
Hashtable在C++的STL里占据着比较重要的一席之地。其中的hash_set、hash_map、hash_multiset、hash_multimap四个关联容器都是以hashtable为底层实现方法(技巧)。应该说,上述的四个关联式容器提供的api都是对hashtable原生态api的高层封装,因为hashtable本身都提供了它们所需要的基础api。接下来,说说自己对hashtable实现技巧的理解和总结吧!
\\
\\
Hashtable底层实现是通过开链法来实现的,hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。而节点元素为自定义的结构体:
template<class Value>
struct __hashtable_node{
__hashtable_node* next;
Value val;
};
可以看到,这本身就是一种很典型的链式列表元素的表示方法,通过当前节点,我们可以很方便地通过节点自身的next指针来获取下一链接节点元素。
\\
\\
在hashtable的实现过程中,我们会认识到一个专有名词:负载系数(loading factor),意指元素个数除以表格大小。很显然,通过开链法,负载系数会大于1。同样在开链方法中,用于装载桶元素的vector容器大小恒定为一个质数大小,在具体实现中表现为28个从小到大的28个质数,如:53、97、193、389。。。等等。每次重新分配vector容器大小时,总是将新容器大小设定为第一个大于当前需要的新容器大小的质数值(上述28个质数中的其中一个)。接下来,我们来谈谈hashtable中几个比较重要的原生态api的实现原理吧。具体的实现技巧建议查看stl源码。
\\
\\
resize(…) 方法:此api主要用于判断当前装载桶元素的vector容器是否需要重建(或者说是扩容),api的唯一参数即为当前桶元素所链接所有节点个数,即链表节点个数(包括当前桶元素),假定为new_num。将new_num值与当前装载桶元素vector容器的大小进行比较,如果大于则进行容器扩容,依次将所有的节点元素都根据其本身的hash值放置到新的容器对应的桶元素所在的链表中,每次都是从头部进入插入操作,如果小于或者等于,则直接插入到其本身属于的桶元素所在的链表头部中。从中我们可以看到每个桶元素所在的链表的元素个数最多为装载桶元素的vector容器的容量。否则就要进行重建工作。
\\
\\
insert_unique_noresize(…) 方法:此api主要用于往hashtable中插入新值,同时新值不允许重复,否则就不插入,立刻返回。事实上,在正式开始执行此方法之前,首先需要运行前一个api:resize()方法,确定vector容器是否需要重建。需要的话则执行,否则再是执行当前api。原理很简单,第一步,根据待插入的新值运行内置函数:bkt_num(new_value),此方法主要是用于确定当前新值归属于哪个桶元素所在的链表中,方便之后插入操作。获取其所在的桶元素之后,依次遍历当前桶元素所在的链表,检查是否有与待插入的新值重复的元素,如果有,则直接退出当前函数,如果没有,则将待插入的新值插入当前链表的头部,完成新值的插入过程。
\\
\\
insert_equal_noresize(…) 方法与insert_unique_noresize()原理类似,唯一不同的地方只是可以插入重复值,即若发现相同值,则直接将待插入新值插入到当前节点的后面即可。否则仍然插入到当前链表的头部位置。探究到这里,想必大家都已经知道呢,hashtable内置的api实现方式并不是很复杂,无非主要是对链表的操作,比如查找、插入,删除等操作。
insert_unique_noresize 向上封装为 insert_unique,而 insert_equal_noresize 向上封装为 insert_equal。这两个 api 都先调用 resize(),然后在调用对应的 insert_xxx_noresize()。
\\
\\
clear(…) 函数实现方式也主要就是依次删除各个桶元素所在的链表中的节点元素即可(释放空间)。但是注意,vector 容器空间不会释放,仍保有原来的大小
\\
\\
copy_from(…) 函数,将另一个同类型的 hash_table ht复制到己方。实现方式是先调用己方的 clear(),然后将己方的 vector 大小 reserve 为 ht 中的 vector 大小,然后依次复制所有 bucket。
\\
\\
经过上述分析和总结,可以看到,虽然stl本身实现很复杂,但是其实里面真正的实现细节并非我们大多数人想像的那样不可理解,只要大家敢于跨出第一步(探究源码的第一步),相信离真像就会越来越近。最后,以《stl 源码剖析》一书前言的一句经典结束今天的hashtable的探索之旅吧!——源码之前,了无秘密!与大家共勉吧!
hash_set 和 hash_multiset 的实现就完全建立在 hashtable 的基础上,几乎其所有成员函数都是直接转调用 hashtable 中的函数。唯一的区别是,hash_set 使用的是 insert_unique,而 hash_multiset 调用的是 insert_equal。
\\
\\
hash_map 和 hash_multimap 也是如此。
\\
\\
而这两种的 set 和 map 之间的区别则是,set 的 key 和 value 是相同的;而 map 的 key 即 key,value 为 pair<const key, T>,T 是与 key 关联的类型