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) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
在擴容的過程中需要進行ReHash操作,而這是非常耗時的,在實際中應該儘量避免。