淺談HashMap底層基本原理

    相信大家對HashMap應該都不陌生吧,應該算是經常使用的集合類型了,最近因有看相關的文章,其實網上有很多的相關資料,我只是將自己理解的記錄下來,在這裏跟大家一起分享與學習

     首先我們來說下HashMap(基於jdk1.8),必然要知道HashMap 是什麼(瞭解它的數據結構),有什麼用(特點及優勢)?

   首先需要了解下jdk1.7及之前hashMapd的底層是(數組+ 鏈表) jdk1.8及之後是(數組+鏈表+紅黑樹(提升查詢效率))      

在Java中,最基本的數據結構其實也就2中(一個是數組,一個是鏈表),個人覺得大部分的數據結構都是由着2種結構來構造的。其實瞭解過HashMap的人都知道,hashMap其實底層是由:數據+鏈表 實現的,有的叫:鏈表散列;如下圖

 

上面的Node就是數組中的元素,它是由一個next向下的引用,這就構成了(單向)鏈表。

   當我們在put 一個元素的時候,其實是根據key的hash值,去獲取該元素在數組中的index(下標)的,然後將該元素放入到對應的位置中去,如果該位置有值了怎麼辦?爲什麼該位置會有值? 這些我們接下的會說到的。如果該位置已經有值了,那麼就已鏈表的形式新加入的放表頭(鏈頭),最後加入的就放鏈尾。當我們在get的時候,首先會計算該key的hsahcode,在數組中找到對應index的元素,然後再通過key的equal的方法,找到需要的數據value,這個地方如果是鏈表的形式的話,那麼在每個鏈表只有一個數據的時候,那效率還是挺高的,但是如果每個鏈表都有很多的值,那這個效率就很尷尬了(這也就是在jdk1.8之後做的一些優化,當鏈表的數據達到一定的數量的時候,就會將鏈表轉爲:紅黑樹o(logn)的效率)。

 那我們先來聊一聊hash算法(我們可以計算到下標(index),需要通過key的hash值來獲取下標的),那是如何計算該位置的hash算法呢,首先這個下標既然是通過人工算出來的,那麼就會有衝突問題,那麼我們需要做的就是儘可能的講這些數據分佈的均勻些,儘量散落的均勻,這樣就會減少每個鏈表的數據量,也就加快的hashmap的效率。

一般情況下,我們可以將每個位置的存儲理解爲一個二進制比如:15  - >  1111 存儲的方式: 當位置爲1的時候,說明該位置有值,有些人可能會問,爲什麼要減1 ,這就問道點上了,比如:一個長度的16的下標範圍是(0~15),這樣的下標範圍,相信大家應該裏理解爲什麼,需要減1 如果  不減的話 就是  0001 0000 ,這樣是不是有些下標位置永遠也不會有值的(如:0001,0010,0011,0101,1001,1011,1101),這樣的話那麼,空間的浪費就大了,而且也會導致鏈表的數據會多,導致查詢效率降低.

^按位異或運算,只要位不同結果爲1,不然結果爲0;
>>> 無符號右移:右邊補0

異或主要就是:能更好的保留各部分的特徵,如果採用&運算計算出來的值會向1靠攏,採用|運算計算出來的值會向0靠攏

爲什麼槽位必須是2^n

原因:1、爲了讓哈希後的結果更加均勻 。2、可以通過位運算e.hash & (newCap - 1)來計算,a % (2^n) 等價於 a & (2^n - 1)  ,位運算的運算效率高於算術運算,原因是算術運算還是會被轉化爲位運算)

這個原因我們繼續用上面的例子來說明

假如槽位數不是16,而是17,則槽位計算公式變成:(17 - 1) & hash

計算了槽位(下標),就來聊聊如何擴容的

擴容(達到擴容的臨界值,提高hashMap的查詢效率,數據量一多hash碰撞就增加,因爲如果數據量達到一定的量時,就會每個node節點就會產生很多值,就需要去變量,需要通過擴容保證效率問題)

1、什麼時候擴容?

jdk1.7  (a、存放新值時,當前已有的值必須大於等於閥值(容量n【默認16】 * 負載因子【默認0.75】

             b、當然也有可能存儲更多值(超多16個值,最多可以存26個值)都還沒有擴容,前11個節點都發生了hash碰撞,都存在某一節點,總共有15個node節點每個節點存值,最多:11+15個值,當第27個值進來時,則再發生擴容)

jdk1.8 (a、大於閥值,就擴容  注意:如果當前值不是新增,而是替換已有的值,那麼也就不會發生擴容)

 

2、如何擴容?

 步驟:1、創建一個新的Entry空數組,長度是原數組的2倍。

           2、ReHash:遍歷原Entry數組,把所有的Entry重新Hash到新數組。爲什麼要重新Hash呢?因爲長度擴大以後,Hash的規則也隨之改變 

JDK1.7中rehash的時候,舊鏈表遷移新鏈表的時候,如果在新表的數組索引位置相同,則鏈表元素會倒置,但是從上圖可以看出,JDK1.8不會倒置, 在舊數組中同一條Entry鏈上的元素,通過重新計算索引位置後,有可能被放到了新數組的不同位置上

推薦位大神的博客:https://blog.csdn.net/pange1991/article/details/82347284  個人覺得例子寫的挺好的

 

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