1. 程式人生 > >面試高頻問題:HashMap實現原理

面試高頻問題:HashMap實現原理

今天給同學們講講一個面試經常遇到的高頻問題,HashMap實現原理,希望在金三銀四的季節對同學們有幫助。

HashMap結構圖目錄
 一、嘮叨
 二、解析思路

 三、get方法
 四、put方法
 五、resize方法

 一、嘮叨


認真閱讀了下HashMap的實現方式,也參考了網上別人的一些解析,個人覺得還是有些東西想說。網上有的文章名字為HashMap原始碼解析,實際上就是給它裡面的一些方法加上一些註釋而已,有不少都是這樣的。
我自己看原始碼的時候,發現不是別人不想解析,而是它的實現真的需要親自研讀,多理順幾遍才知道怎麼回事。
我在這裡解析的文字描述也較多,不管誰的解析,自己也都要看一下JDK原始碼的具體實現,我們僅提供參考而已。

二、解析思路


原始碼不太方便看,先說明一下我的閱讀思路。
1.把常用的幾個方法拷貝到文字編輯器裡面。
2.HashMap中不同的時候會有不同的流程,梳理方法中的邏輯流程。就像採用極端法,採用特殊的資料,然後檢視方法執行語句。未執行的語句暫時不考慮。
3.註釋原始碼...我覺得HashMap的實現方式不夠好,關鍵的幾個方法裡面包含的情況太多了,閱讀起來是有難度的,而寫程式的目的之一不就是讓其他開發者閱讀嗎?一個方法內部做了太多的事情,違反了程式碼整潔的規則,一個函式做要儘量少的事情。
解析
之前稍微介紹了一些HashMap的特性,HashMap初探。
(https://www.jianshu.com/p/be9ffb76db30)這裡接著深入。

三、get方法

先挑最簡單的說

 

 

1 先從陣列下標,找到對應的Node2. 
2 如果Node裡的第一個節點命中,直接返回
3 如果有衝突,則通過key.equals(k)去查詢對應的entry
4 若為樹,則在樹中通過key.equals(k)查詢,O(logn);
5 若為連結串列,則在連結串列中通過key.equals(k)查詢,O(n)。put方法這個中間涉及的邏輯多一些,方法需要分不同的步驟看。

 


 

四、put方法


這個中間涉及的邏輯多一些,方法需要分不同的步驟看。
思路:
1對key的hashCode()做hash,然後再計算index;
2如果沒碰撞直接放到bucket裡;
3如果碰撞了,以連結串列的形式存在buckets後;
4如果節點已經存在就替換old 5value(保證key的唯一性)
6如果碰撞導致連結串列過長(大於等於TREEIFY_THRESHOLD),就把連結串列轉換成紅黑樹;
7如果Node的容量滿了(超過load factor*current capacity),就要resize。



 

一般不發生碰撞的時候,相對簡單,資料量較小的情況下。

 

 

我解釋下關於碰撞衝的迴圈。
1.檢視是否存在相同的key,存在相同的key跳出迴圈,覆蓋key的value。
2.如果不存在相同的key,在連結串列末尾插入新的Node如果連結串列節點過長,轉換為樹。
3.如果連結串列節點過長,轉換為樹。

 

 

紅黑樹的部分,我們下次單獨解析

 五、resize方法


這個涉及的內容,有不少線需要捋一捋。首先看申明時候會resize()。它們都在呼叫put的時候執行的。

1.table == 的時候

 

 

2.鍵值對映的的數目大於臨界值的時候。

 

 

六、resize具體方法


 

 

如果是第一次resize,我們抽出來會執行到的語句。
1.初始化容量
2.初始化threshold,也就是初始化臨界值,決定了table的鍵值對數目到什麼時候會再次resize()


 

第二次及後續的resize執行流程

 


 

resize中對有碰撞的連結串列的操作寫的很有意思,再敘述一下。在重新分配索引的時候,有重新組建連結串列的操作。

舉個比較誇張的例子,讀者就明白了。
1.e.hash < 2,那麼e.hash&oldCap就等於0,索引為小於之前hash表大小以內的索引。也就是當初的索引不變。
2.e.hash > 2的時候,e.hash&old不等於0,那麼它的索引就為當前表的索引再加上新擴容的大小。


                案例圖

這個圖說的是,當hashmap的表大小為2擴充到4的時候,原本掛載在1位置的連結串列,重新分配之後的樣子。

最後
篇幅有限,我這裡僅僅介紹了get方法,put方法,resize方法的具體原理,文章就已經非常長了,不利於閱讀。
下次再補充一下HashMap的hash方法原理,其餘的