HashMap源碼分析

       HashMap作爲最常用的容器類之一不一定每個人都對它非常瞭解。即使面試題也僅僅會比較幾種容器的性能和線程安全問題。

       HashMap如此常用確實有着它精巧的設計,即使我現在也無法真正理解其hash()策略是如何想到的。這裏就把我一些理解的寫出來,一方面是自己的筆記,另一方面是可以和廣大程序員作一個交流,說不定能解開我一些無法解答的迷。

       這裏先提幾個問題作爲引子。

1.       HashMap是以什麼爲基礎結構來作到快速定位。

2.       HashMap是如何解決Key衝突的。

3.       如何保證數組的容量在控制範圍內(就是數組的下標是自己能掌控的)

4.       HashMap的散列算法。

5.       HashMap的一些弊端。

       這幾個問題如果擴展開來講都代表着一些思想在裏面。好,先從第一個開始。其實我們都知道Map有着Key-Value鍵值對,這樣就可以保證快速定位,這些思想在硬件內存索引,搜索引擎,數據庫中都有着類似的具體應用。這裏就不展開來講。下面就是要知道在java的基礎類中有哪些可以滿足這樣的要求,可以直接根據key找到value。可能一時想不到數組,或許很多人沒等想出答案已經在不同途徑知道答案了。數組(PS:Java中數組並不是一個常用的結構,一般都會用List來代替)。數組正有這樣的特性。其下標正是我們想要的key

       這裏又會出兩個新問題:a)keyInteger類型;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還有其他方法,有興趣的可以慢慢看。     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章