一起看HashMap源碼

一起看HashMap源碼

寫在前面

HashMap源碼分析,可以說是面試中必問的題目。作為以key/value存儲方式的集合,HashMap的集成了鏈表的思想;此外1.8引入了紅黑樹的思想。今天這篇文章著重聊一聊1.8之前的HashMap~

接下來會用到的幾個常量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

static final int MAXIMUM_CAPACITY = 1 << 30;

static final int MAXIMUM_CAPACITY = 1 << 30;

正文

HashMap的思路

我們put的key/value會被封裝成一個叫做Entry的內部類。這個Entry由一個變量名為table數組管理。我們每次put會通過一系列的計算,計算一個table數組的index下標用於放Entry,如果出現hash衝突使用鏈表法解決。get時,可以理解是一個反向的put過程。


put(K key, V value)

1、初始化

一起看HashMap源碼

走到這一步,相當於我們的第一次put的初始化過程完成。那麼接著讓我們看下一步操作。


2、key為null

當然這一步的前面還有一個key為null的情況。因為實在是太直白,就不單獨展開,代碼如下:

一起看HashMap源碼

這裡我們可以得到一個信息,key為null的Entry會放在table[0]的位置上。


3、index下標計算

走到這一步,我們所要做的就是先計算我們這個key/value應該放在table的那個位置。也就是說,在真正包裝成Entry之前,我們需要確定這個Entry的應該再哪個坑裡。

計算hash值的hash方法如下:

一起看HashMap源碼

計算完hash之後,就是通過hash計算我們Entry應該在那個下標中:

一起看HashMap源碼

走到這一步我們Entry該放在哪個位置已經明確了,這裡有很多位運算…根據效果來看,這樣的計算方式保證了,Entry更少的衝突。


4、插入Entry

既然上訴2的過程已經確定了插入位置,那麼毫無疑問,我們該插入這個Entry了。

4.1、重複key處理

既然插入,那麼勢必有可能遇到重複問題,比如說,我們插入同一個key。這裡就是一個比較常見的問題,Map可不可以使用重複key,或者Map怎樣處理重複key的問題。

一起看HashMap源碼

因此關於key重複的問題,我們就可以得到答案。Map的操作是替換舊的value並返回老的value。

4.2、擴容

上訴步驟我們處理的key重複的問題。那麼接下來,就是Map的擴容過程。這裡會用到一個變量threshold,我們知道初始化table之後,這個變量 = 數組長度*0.75。記住這個值,它就是擴容的閾值。

一起看HashMap源碼

4.3、創建並添加

一起看HashMap源碼

首先獲取index下標下的Entry,我們明白這裡的e有可能為空。(而這裡沒有對null這種情況進行判斷,也就是說這裡為不為空都無所謂)

拿到e之後,進行new Entry()把e,以及hash,key,value傳了進來。

這裡我們看一個e,放在了哪裡?

一起看HashMap源碼

這裡說明一個什麼問題?那就是當hash衝突之後,我們的Entry是在鏈表的頭還是尾。根據代碼來看,很明顯是在鏈表的頭,這也說明了,為什麼e為null這種情況沒有做特別處理。

我們對put的分析就到此為止,既然分析了put,那麼接下來就是get。

get(Object key)

1、處理key為null的情況

一起看HashMap源碼

關於key為null的情況,其實我們也能看出,比較簡單明瞭。接下來就是key不為null的情況。


2、key不為null

一起看HashMap源碼

代碼思路比較的明確,其實就是一個反向的put過程。我們先通過key計算hash,然後計算對應Entry在數組中的index,然後遍歷對應鏈,找出匹配的value即可。

get方法我們可以看出,是比較簡單的。


尾聲

分析完put/get其實基本上HashMap就梳理完畢。

這裡我們進行一點總結:

  • 1、put時,key可以為空。並且放在table[0]的這個位置
  • 2、擴容策略是,size>=當前容量*0.75並且當前table[index]不為null。擴容大小為2倍。
  • 3、JDK1.7的HashMap使用鏈表法解決衝突,並且新插入的Entry是在鏈表的表頭。


分享到:


相關文章: