STL hashtable 的底層實現,與 hash_set, hash_map, hash_multiset, hash_multimap

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 關聯的類型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章