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表被申明爲了數組,如如下代碼所示:app
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有必定的優化。this
put函數中用到的兩個函數hash和indexFor其實現分別以下:spa
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以內。code
固然,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操做,而這是很是耗時的,在實際中應該儘可能避免。