1. 程式人生 > >C++ map和HashMap原理詳解

C++ map和HashMap原理詳解

一、Map成員

上面可以看到Map介面的幾個實現方式。簡要說明:

TreeMap是基於樹(紅黑樹)的實現方式,即新增到一個有序列表,在O(log n)的複雜度內通過key值找到value,優點是空間要求低,但在時間上不如HashMapC++Map的實現就是基於這種方式

HashMap是基於HashCode的實現方式,在查詢上要比TreeMap速度快,新增時也沒有任何順序,但空間複雜度高。C++ unordered_Map就是基於該種方式。

HashTableHashMap類似,只是HashMap是執行緒不安全的,HashTable是執行緒安全的,現在很少使用

ConcurrentHashMap也是執行緒安全的,但效能比HashTable好很多,HashTable是鎖整個Map物件,而ConcurrentHashMap是鎖Map的部分結構

二、HashMap詳解

HashMap簡稱雜湊表,下面介紹下主要思想和流程。

HashMap在新增值是需要給定兩個引數,一個是key,一個是value。為了能很快的通過key值找到對應的value,因此有必要建立一個key值和記憶體指標的對映,舉個簡單的例子,如果說key值是int型,那麼其實最簡單的方式就是定義一個數組,以這個key值作為下標,value作為記憶體中的值。然而由於key值可能會很大,或者是string或著其他型別的值,因此就不能單純的簡單對應了,這時候就需要做一個轉換。這個在Java和C#中是通過一個int HashCode()的函式實現的。具體的實現可能是通過地址、字串或數字算出來的值,然後如果是自己定義的物件,則需要自己實現HashCode()和equal().

注意,hashcode的實現需要滿足以下要求:

1、如果兩個物件equals相等,那麼這兩個物件的HashCode一定也相同

2、如果兩個物件的HashCode相同,不代表兩個物件就相同,只能說明這兩個物件在雜湊儲存結構中,存放於同一個位置

好,那麼在計算出hashcode之後再怎麼做呢,由於hashcode算出來的值可能很大,定義一個大小能包含所有hashcode的陣列顯然是不合理的。在實際的實現是這樣的,事先定義一個大小為2的冪次方的陣列(稍後解釋為什麼是2的冪次方)。為了能保證所有的hashcode都能對應到陣列的下標,可以採用hashcode對陣列大小(一般稱為bucket)取餘的方式。而具體的實現就是:

static int indexFor(int h, int length) {  
    return h & (length-1);
}

通過按位與運算巧妙的求得了餘數,並且很大程度上減少了運算效率。但由於可能會有多個key值對應同一個index,為了避免衝突,其實每個陣列元素裡儲存的是連結串列結構。當新增函式檢測到index對應的元素已經有值了以後,它就會將key值和value作為子節點新增到該index所在元素的尾部節點。如果檢測到key值相同,則更新value。

當連結串列的長度大於8後,會自動轉為紅黑樹,方便查詢。如果hashMap裡的元素越來越多,那麼衝突的概率會越來越大,因此有必要即時的對陣列長度擴容。當HashMap中的元素個數超過陣列大小(陣列總大小length,不是陣列中個數size)*loadFactor時,就會進行陣列擴容,loadFactor的預設值為0.75,這是一個折中的取值。也就是說,預設情況下,陣列大小為16,那麼當HashMap中元素個數超過16*0.75=12(這個值就是程式碼中的threshold值,也叫做臨界值)的時候,就把陣列的大小擴充套件為2*16=32,即擴大一倍,然後重新計算每個元素在陣列中的位置,而這是一個非常消耗效能的操作,所以如果我們已經預知HashMap中元素的個數,那麼預設元素的個數能夠有效的提高HashMap的效能。擴容的操作是這樣的:

int capacity = 1;
    while (capacity < initialCapacity)  
        capacity <<= 1;

這個就表示,每次擴容都是在原有的基礎上×2,這也就是為什麼大小是2的冪次的原因。