HashMap的內部實現機制

SaiW-n_n-發表於2017-05-17

1.HashMap的內部實現機制

HashMap是對資料結構中雜湊表(Hash Table)的實現, Hash表又叫雜湊表。Hash表是根據關鍵碼Key來訪問其對應的值Value的資料結構,它通過一個對映函式把關鍵碼對映到表中一個位置來訪問該位置的值,從而加快查詢的速度。這個對映函式叫做Hash函式,存放記錄的陣列叫做Hash表。

在Java中,HashMap的內部實現結合了連結串列和陣列的優勢,連結節點的資料結構是 Entry<k,v>,每個Entry物件的內部又含有指向下一個Entry型別物件的引用,如以下程式碼所示:

static class Entry<K,V> implements Map.Entry<K,V> {  
      final K key;  
      V value;  
      Entry<K,V> next; //Entry型別內部有一個自己型別的引用,指向下一個Entry  
      final int hash;   
      ...
}  

在HashMap的建構函式中可以看到,Entry表被申明為了陣列,如以下程式碼所示:

public HashMap() {  
        this.loadFactor = DEFAULT_LOAD_FACTOR;  
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  
        table = new Entry[DEFAULT_INITIAL_CAPACITY];  
        init();  
    }  

在以上建構函式中, 預設的 DEFAULT_INITIAL_CAPACITY值為16,DEFAULT_LOAD_FACTOR的值為0.75。

當put一個元素到HashMap中去時,其內部實現如下:

public V put(K key, V value) {  
        if (key == null)  
            return putForNullKey(value);  
        int hash = hash(key.hashCode());  
        int i = indexFor(hash, table.length);  
        ...    
}  

可以看到put函式中用一個hash函式來得到雜湊值,需要指出的是,HashTable在實現時直接用了hashCode作為雜湊值,因此採用HashMap代替HashTable有一定的優化。

put函式中用到的兩個函式hash和indexFor其實現分別如下:

static int hash(int h) {  
        // This function ensures that hashCodes that differ only by  
        // constant multiples at each bit position have a bounded  
        // number of collisions (approximately 8 at default load factor).  
        h ^= (h >>> 20) ^ (h >>> 12);  
        return h ^ (h >>> 7) ^ (h >>> 4);  
    }  
     /** 
     * Returns index for hash code h. 
     */  
    static int indexFor(int h, int length) {  
        return h & (length-1);  
    }  

至於hash函式為什麼這樣設計,這涉及到具體雜湊函式的設計問題了,需要考慮的是雜湊演算法的時間複雜度,同時儘量使得陣列上每個位置都有值,求得時間和空間的最優。

indexFor函式則用了一個很巧妙的與運算將index值限制在了length-1之內。

當然,hash函式存在衝突的情況,同一個key對應的hash值可能相同,這時候hash值相同的元素就會用連結進行儲存,HashMap的get方法在獲取value的時候會對連結串列進行遍歷,把key值相匹配的value取出來。

2.Hash的實現

主要是雜湊演算法和衝突的解決。

3.什麼時候ReHash

在介紹HashMap的內部實現機制時提到了兩個引數,DEFAULT_INITIAL_CAPACITY和DEFAULT_LOAD_FACTOR,DEFAULT_INITIAL_CAPACITY是table陣列的容量,DEFAULT_LOAD_FACTOR則是為了最大程度避免雜湊衝突,提高HashMap效率而設定的一個影響因子,將其乘以DEFAULT_INITIAL_CAPACITY就得到了一個閾值threshold,當HashMap的容量達到threshold時就需要進行擴容,這個時候就要進行ReHash操作了,可以看到下面addEntry函式的實現,當size達到threshold時會呼叫resize函式進行擴容。

void addEntry(int hash, K key, V value, int bucketIndex) {  
ntry<K,V> e = table[bucketIndex];  
      table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
      if (size++ >= threshold)  
          resize(2 * table.length);  
  }  

在擴容的過程中需要進行ReHash操作,而這是非常耗時的,在實際中應該儘量避免。

 (原創文章,轉載請註明作者schbook:seekerxu@163.com)

相關文章