1. 程式人生 > >【JAVA】HashMap底層實現原理淺談

【JAVA】HashMap底層實現原理淺談

                                  HashMap底層實現原理淺談

不論是實習還是正式工作,HashMap的底層實現原理一直是問地頻率最高的一個內容,今天記錄一下自己對HashMap的理解,如有不當之處,還請各位大佬指正。

一、前置名詞解釋

(1)雜湊表

雜湊表的主體軀幹就是陣列,我們可以利用下標對元素進行快速索引。新增一個元素時,元素的插入位置由雜湊函式來定。

(2)雜湊演算法/雜湊函式

雜湊函式的作用是將一個不固定長度的二進位制值經過運算,轉化為一個固定長度的二進位制值。將插入元素的key進行運算後,得到某個index值,從而確定在陣列中的儲存位置。但多個不相同的元素,經過雜湊函式運算後,可能得到相同的index,這就出現了雜湊衝突。

(3)雜湊衝突/雜湊碰撞

再好的雜湊演算法在有限長度的雜湊表上,都會造成雜湊衝突。那麼怎麼解決衝突呢?有多種解決方案

【1】開放地址法:放棄本次運算得到的index,繼續尋找下一塊未被佔用的空間

【2】再雜湊法

【3】鏈地址法:採用陣列(雜湊表主體)+連結串列(為了解決雜湊衝突而存在)的形式,雜湊表中儲存陣列,陣列中的元素空間儲存連結串列的頭結點。當發生衝突時,將相應的index位置上舊元素的指標next指向新元素即可。HashMap採用的就是這種方法

二、HashMap的特點

(1)允許存放null鍵和null值,而HashTable都不允許

(2)不是執行緒安全的,而HashTable是執行緒安全的,當然我們可以使用ConcurrentHashMap來代替HashTable,而且ConcurrentHashMap的效能更好,因為它只對陣列的一部分進行上鎖,即分段鎖,而不是鎖住整個陣列。

(3)陣列的預設大小為16,原始碼中沒有直接寫16,而是1<<4。

  /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

(4)預設的負載因子是0.75,表示的是,如果陣列中已經儲存的元素個數大於陣列長度的75%,將會引發擴容操作。

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

(5)擴容後,新陣列的長度為原來陣列的兩倍。陣列的預設大小與擴容倍數共同決定了陣列長度永遠是一個2的冪。

三、HashMap的實現原理

(1)HashMap的主幹

HashMap的主幹是一個Entry陣列,裡面存放Entry物件,每一個Entry物件包含(key,value)鍵值対和next指標以及對應的hash值。[jdk1.8之前用的是Entry,jdk1.8之後用的是Node,兩者基本等價]

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
      .........
 }

(2)put(key,value)的過程

【1】由key計算雜湊值

【2】由雜湊值計算在陣列中的索引

【3】如果索引處的Entry為null的話,則直接在此處插入元素。如果索引出的Entry不為null的話,通過迴圈不斷遍歷連結串列查詢是否有相同雜湊值的key,如果有,再比較兩個key的是否相同,當雜湊值與key都相同時,則認為是同一個Entry物件並覆蓋原物件的value值。當雜湊值相同而key不相同時,則認為不是同一個Entry物件,那麼在連結串列尾部新增元素。[jdk1.8之前插入連結串列頭部,jdk1.8之後插入連結串列尾部]

當索引位置相同的key超過8個時,即連結串列長度大於8時,jdk1.8會將此連結串列轉化為紅黑樹。

(3)get(key)的過程

【1】由key計算雜湊值

【2】由雜湊值計算在陣列中的索引

【3】迴圈遍歷連結串列,如果有某一個Entry的key與雜湊值與搜尋的key、雜湊值都相同的話,則取出此key對應的value。

(4)擴容的過程

當陣列中已經儲存的元素個數大於陣列長度的負載因子倍數時,將會引發擴容操作。

【1】建立一個長度為原來陣列長度兩倍的新陣列。

【2】重新對原陣列中的Entry物件進行雜湊運算,以確定他們各自在新陣列中的新位置。

當然擴容的過程也會有很多問題,比如在多執行緒的情況下,多個執行緒同時對陣列進行擴容時,有可能會造成條件競爭,就會發生意料之外的結果。因此在多執行緒的情況下,還是得使用執行緒安全的HashMap,比如ConcurrentHashMap,或者是經過Collections.synchronized(HashMap<K,V>)封裝後的HashMap。

四,總結

HashMap可以用在快取處理,動態規劃中的備忘錄等,需要注意的是,HashMap不是執行緒安全的。

礙於博主才疏學淺,談到的HashMap原理非常的粗淺,博主定會在日後的學習與工作中,不斷的完善博文,努力提高部落格的水準。