HashMap作爲最常用的容器類之一不一定每個人都對它非常瞭解。即使面試題也僅僅會比較幾種容器的性能和線程安全問題。
HashMap如此常用確實有着它精巧的設計,即使我現在也無法真正理解其hash()策略是如何想到的。這裏就把我一些理解的寫出來,一方面是自己的筆記,另一方面是可以和廣大程序員作一個交流,說不定能解開我一些無法解答的迷。
這裏先提幾個問題作爲引子。
1. HashMap是以什麼爲基礎結構來作到快速定位。
2. HashMap是如何解決Key衝突的。
3. 如何保證數組的容量在控制範圍內(就是數組的下標是自己能掌控的)
4. HashMap的散列算法。
5. HashMap的一些弊端。
這幾個問題如果擴展開來講都代表着一些思想在裏面。好,先從第一個開始。其實我們都知道Map有着Key-Value鍵值對,這樣就可以保證快速定位,這些思想在硬件內存索引,搜索引擎,數據庫中都有着類似的具體應用。這裏就不展開來講。下面就是要知道在java的基礎類中有哪些可以滿足這樣的要求,可以直接根據key找到value。可能一時想不到數組,或許很多人沒等想出答案已經在不同途徑知道答案了。數組(PS:在Java中數組並不是一個常用的結構,一般都會用List來代替)。數組正有這樣的特性。其下標正是我們想要的key。
這裏又會出兩個新問題:a)key是Integer類型;b)如何保證key的唯一性。這就是我上面所提出的第二個問題。爲了使key成爲Integer類型,可以用一定的算法讓各種類型映射成Integer類型,其實這裏難就難在如何有一個算法讓不同類型的值能映射成Integer的時候唯一,至少保證大部分唯一。這裏聯想到hashCode可以滿足這個要求,而且我們也可以實現自己的hashCode方法來達到這個目的。這樣第二個問題就解決了。
這裏就會到第三個問題,因爲hashCode雖然可以實現唯一,但是下一個問題就出現了,那數組下標如何保證,我們不可能申請一個無限大的內存,而且hashCode是有負數的。這樣就引出了HashMap第四個問題,它精巧的hash算法。關於HashMap中的hash()方法是根據位移來保證高位參與^的運算。這是因爲當數組申請的容量很小的時候(HashMap默認是16)。很容易造成key衝突。
這樣講估計很難講清楚,我先把代碼貼出來:
第一個是它的hash算法,這裏通過位移的方式讓高位參與運算,很多人會問爲什麼要這樣做,這裏就是爲了解決key衝突的算法。
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);
}
第二個看一下它另外一個精巧的設計,就是如何保證數組key是保證在可控的範圍內。這個方法在我第一次看到的時候真心感覺這個設計太精巧了。
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
這段代碼是put()中的一段,就是用來判斷是否有相同值,如果hashCode和key都相同,說明確實相同。極端情況下要把餘下的數組掃描完。所以說一個好的哈希算法的重要性。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
這兩個方法其實要合併起來看,第一個方法是保證第二個方法不至於過多的key衝突。這裏舉個例子,如果HashMap的容量是16,那麼(length-1)就是15,二進制是1111,這樣它可以存儲key從0~15的值,比如我要存儲下標爲1這個值,這樣不通過hash算法將會出現大量的key衝突,比如說二進制0001,10001,110001這樣只要低四位是0001的hashCode都可以滿足這樣要求。hash算法通過高位右移來參與低位的運算以求保證低位的不同。至於具體爲什麼可以達到這樣的目標我也說不上來,看了半天也無法說出個理來。即使這樣也不能保證百分之百的不衝突,只是概率會降低很多。
好,第三個問題就到此爲止。
第四個問題。關於HashMap的一些弊端。第一個就是對於數組利用率,由於hashCode接近於隨機性,導致對於數組的利用率並不能到達100%。大概只有70%左右。第二個就是當刪除數據後數組無法彈性伸縮。
HashMap還有其他方法,有興趣的可以慢慢看。