01.23 面試必考之HashMap源碼分析與實現

面試必考之HashMap源碼分析與實現


下面代碼分析時基於 jdk1.8 的

HashMap 是什麼?


說先我們看一下HashMap的基本用法:

<code>Map<string> map = new HashMap<>();
\t\tmap.put("hello", "world");
\t\tSystem.out.println(map);/<string>/<code>

我們從put操作開始分析。源碼如下:


面試必考之HashMap源碼分析與實現

點進入 putVal 方法:


面試必考之HashMap源碼分析與實現

如果你直接看上面的代碼,可能會懵掉。

下面我們可以換一個思路來思考作者是怎麼實現HashMap的??


HashMap是幹什麼的?

我們從上面用法上可以看出,HashMap 是用來存儲 key -value數據的。

HashMap 是用什麼數據結構來存儲的?


我們可以回憶一下 ArrayList 和 LinkedList 的數據結構。


我們從ArrayList 的add 方法看起:


面試必考之HashMap源碼分析與實現


面試必考之HashMap源碼分析與實現

從上面可以看出 ArrayList 使用的是 數組實現。


下面看下 LinkedList add 方法:


面試必考之HashMap源碼分析與實現

Node 節點定義如下:


面試必考之HashMap源碼分析與實現

LinkedList 節點定義

所以 LinkedList 使用的是雙向鏈表。

HashMap 數據結構 = 數組 + 單向鏈表,如下圖

面試必考之HashMap源碼分析與實現

HashMap 的數據結構

我們通過看源碼來驗證上面數據結構:

面試必考之HashMap源碼分析與實現

內部類 Node定義

上圖的是HashMap中的Node 節點定義,是一個單向鏈表。

面試必考之HashMap源碼分析與實現

上圖定義Node數組 來放Node節點。

HashMap其他定義


1.Node 數組的定義長度為

面試必考之HashMap源碼分析與實現

默認Node數組大小

默認為16.

2.負載 因子 DEFAULT_LOAD_FACTOR 什麼意思呢?

面試必考之HashMap源碼分析與實現

負載因子

默認負載因子為0.75 ,當存儲的元素越來越多,Node數組存儲的空間越來越少,該怎麼辦呢?

只有擴容,那麼該怎麼擴容?什麼時候擴容呢?

我們知道HashMap默認大小為16,負載因子為0.75

則當容量 > 12時,進行擴容。

3.當鏈表的長度大於一定長度怎麼辦?

當鏈表的長度很長時,會有什麼缺點呢?


面試必考之HashMap源碼分析與實現

如果查詢到的節點是最後一個節點時,則需要遍歷此節點上的所有鏈表中的元素。所以比較耗時。


JDK8 中,當鏈表長度大於一定長度時,就會轉成 紅黑樹。

面試必考之HashMap源碼分析與實現

當 單向鏈表的長度 > 8 時,就會轉成 紅黑樹

面試必考之HashMap源碼分析與實現

  1. 當向Map中put一個元素時,如何知道放在數據中的哪個位置呢?

從上面的數據結構可以看出,先把元素存儲在數組中,如果有重疊,則放在鏈表中。


當put一個key,首先通過獲取 key.hashCode把key生成一個hashCode,然後再經過一些列算法生成0-15之間的數值(因為Map的默認值為16)。

那麼怎樣設計一個算法,把key計算成一個0-15 之間的數據呢?

1.先通過Object的hashCode方法,獲取key的hashCode值

2.然後控制這個值在0-15 之間,可以對16進行取模


put源碼分析


首先我們看下put方法的流程:


面試必考之HashMap源碼分析與實現

流程如下:

1、計算key的hash值


面試必考之HashMap源碼分析與實現

面試必考之HashMap源碼分析與實現

調用Object的hashCode,得到hashCode值,然後 第16位 和 高16位 做異或,得到最終hash值。

2、得到Node節點在數組中的位置

面試必考之HashMap源碼分析與實現

計算邏輯為 (n-1) & hash

為什麼是 n - 1 呢?默認值為16, 15 的二進制為 01111

比如 上面得到的hash值為 :

00000000011111000001110110101

& 01111

--------------------------------------------------------------

0101

如果為n - 1 ,則 hash值的倒數第5位無論為1或者0,和 15的與後,最終的值都在0-15之間。


面試必考之HashMap源碼分析與實現

上面代碼邏輯如下:

1、第一個if: 如果table還沒有初始化,則先進行初始化。

2、第二個if : 如果根據key的hash值,獲取到在數組中的位置,如果為 null,則直接new 一個Node節點,直接放進去。


下面看else代碼:


面試必考之HashMap源碼分析與實現

1、第一個if : 如果原來的位置有節點,且key相同,則直接覆蓋原值即可。

2、else if : 判斷節點是否為 紅黑樹,如果是,則調用紅黑樹添加邏輯。

3、else: 則為鏈表,調用鏈表的處理邏輯


面試必考之HashMap源碼分析與實現

(1) 如果 (e = p.next) == null ,則 直接創建Node節點,如果大於紅黑樹閥值,則轉紅黑樹,然後插入。

(2) if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) break;

如果插入的key 已經存在,則直接跳出,執行下面邏輯。

面試必考之HashMap源碼分析與實現

執行上面的前提是在Map存在此key,則直接替換舊值即可。


面試必考之HashMap源碼分析與實現

最後再次要判斷是否需要擴容??

resize 方法


面試必考之HashMap源碼分析與實現

上面的最初oldCap中為16,(newCap = oldCap << 1 ) 則擴容為當前容量的2倍。

比如:當前oldCap = 16, 16的二進制為 1000,左移一位為:10000,= 32


面試必考之HashMap源碼分析與實現

容量變的同時,threshold 也要變,32 * 0.75 = 24;


下面的操作就是對節點進行重新分配:

面試必考之HashMap源碼分析與實現

有以下幾種情況:

1、有節點,但是下面為空

2、有節點,下面不為空,但是為紅黑樹

3、有節點,下面不為空,但是為鏈表


面試必考之HashMap源碼分析與實現


面試必考之HashMap源碼分析與實現


e.hash & oldCap 是什麼意思呢?

e.hash & oldCap 的結果只有兩個值,要麼為0,要麼為1。

rehash的時候,是根據 e.hash & oldCap 是否為0,來拆分low 和 high 兩個鏈表的。

然後newTab[j] = low,newTab[j+oldCap]= high;

所以resize 後的位置,要麼在newTab[i],要麼在newTab[i + oldTab]


分享到:


相關文章: