HashMap就是這麼簡單【原始碼剖析】

Java3y發表於2018-04-10

前言

宣告,本文用得是jdk1.8

前面已經講了Collection的總覽和剖析List集合以及雜湊表、Map集合、紅黑樹的基礎了:

本篇主要講解HashMap,以及涉及到一些與hashtable的比較~

看這篇文章之前最好是有點資料結構的基礎:

當然了,如果講得有錯的地方還請大家多多包涵並不吝在評論去指正~

一、HashMap剖析

首先看看HashMap的頂部註釋說了些什麼:

HashMap就是這麼簡單【原始碼剖析】

再來看看HashMap的類繼承圖:

HashMap就是這麼簡單【原始碼剖析】

下面我們來看一下HashMap的屬性:

HashMap就是這麼簡單【原始碼剖析】

成員屬性有這麼幾個:

HashMap就是這麼簡單【原始碼剖析】

再來看一下hashMap的一個內部類Node:

HashMap就是這麼簡單【原始碼剖析】

我們知道Hash的底層是雜湊表,而在Java中雜湊表的實現是通過陣列+連結串列的~

再來簡單看看put方法就可以印證我們的說法了:陣列+連結串列-->雜湊表

HashMap就是這麼簡單【原始碼剖析】

我們可以簡單總結出HashMap:

  • 無序,允許為null,非同步
  • 底層由雜湊表(雜湊表)實現
  • 初始容量和裝載因子對HashMap影響挺大的,設定小了不好,設定大了也不好

1.1HashMap構造方法

HashMap的構造方法有4個:

HashMap就是這麼簡單【原始碼剖析】

HashMap就是這麼簡單【原始碼剖析】

在上面的構造方法最後一行,我們會發現呼叫了tableSizeFor(),我們進去看看:

HashMap就是這麼簡單【原始碼剖析】

這是位運算演算法,具體流程可參考:

看完上面可能會感到奇怪的是:為啥是將2的整數冪的數賦給threshold

  • threshold這個成員變數是閾值,決定了是否要將雜湊表再雜湊。它的值應該是:capacity * load factor才對的。

其實這裡僅僅是一個初始化,當建立雜湊表的時候,它會重新賦值的:

HashMap就是這麼簡單【原始碼剖析】

至於別的構造方法都差不多,這裡我就不細講了:

HashMap就是這麼簡單【原始碼剖析】

1.2put方法

put方法可以說是HashMap的核心,我們來看看:

HashMap就是這麼簡單【原始碼剖析】

我們來看看它是怎麼計算雜湊值的:

HashMap就是這麼簡單【原始碼剖析】

為什麼要這樣幹呢??我們一般來說直接將key作為雜湊值不就好了嗎,做異或運算是幹嘛用的??

我們看下來:

HashMap就是這麼簡單【原始碼剖析】

我們是根據key的雜湊值來儲存在雜湊表中的,我們表預設的初始容量是16,要放到雜湊表中,就是0-15的位置上。也就是tab[i = (n - 1) & hash]。可以發現的是:在做&運算的時候,僅僅是後4位有效~那如果我們key的雜湊值高位變化很大,低位變化很小。直接拿過去做&運算,這就會導致計算出來的Hash值相同的很多。

而設計者將key的雜湊值的高位也做了運算(與高16位做異或運算,使得在做&運算時,此時的低位實際上是高位與低位的結合),這就增加了隨機性,減少了碰撞衝突的可能性!

下面我們再來看看流程是怎麼樣的:

HashMap就是這麼簡單【原始碼剖析】

新值覆蓋舊值,返回舊值測試:

HashMap就是這麼簡單【原始碼剖析】

接下來我們看看resize()方法,在初始化的時候要呼叫這個方法,當雜湊表元素大於capacity * load factor的時候也是呼叫resize()

HashMap就是這麼簡單【原始碼剖析】

1.3get方法

HashMap就是這麼簡單【原始碼剖析】

接下來我們看看getNode()是怎麼實現的:

HashMap就是這麼簡單【原始碼剖析】

1.4remove方法

HashMap就是這麼簡單【原始碼剖析】

再來看看removeNode()的實現:

HashMap就是這麼簡單【原始碼剖析】

二、HashMap與Hashtable對比

從儲存結構和實現來講基本上都是相同的。它和HashMap的最大的不同是它是執行緒安全的,另外它不允許key和value為null。Hashtable是個過時的集合類,不建議在新程式碼中使用,不需要執行緒安全的場合可以用HashMap替換,需要執行緒安全的場合可以用ConcurrentHashMap替換

HashMap就是這麼簡單【原始碼剖析】

Hashtable具體閱讀原始碼可參考:

四、總結

在JDK8中HashMap的底層是:陣列+連結串列(雜湊表)+紅黑樹

在雜湊表中有裝載因子這麼一個屬性,當裝載因子*初始容量小於雜湊表元素時,該雜湊表會再雜湊,擴容2倍!

裝載因子的預設值是0.75,無論是初始大了還是初始小了對我們HashMap的效能都不好

  • 裝載因子初始值大了,可以減少雜湊表再雜湊(擴容的次數),但同時會導致雜湊衝突的可能性變大(雜湊衝突也是耗效能的一個操作,要得操作連結串列(紅黑樹)
  • 裝載因子初始值小了,可以減小雜湊衝突的可能性,但同時擴容的次數可能就會變多!

初始容量的預設值是16,它也一樣,無論初始大了還是小了,對我們的HashMap都是有影響的:

  • 初始容量過大,那麼遍歷時我們的速度就會受影響~
  • 初始容量過小,雜湊表再雜湊(擴容的次數)可能就變得多,擴容也是一件非常耗費效能的一件事~

從原始碼上我們可以發現:HashMap並不是直接拿key的雜湊值來用的,它會將key的雜湊值的高16位進行異或操作,使得我們將元素放入雜湊表的時候增加了一定的隨機性

還要值得注意的是:並不是桶子上有8位元素的時候它就能變成紅黑樹,它得同時滿足我們的雜湊表容量大於64才行的~

HashMap就是這麼簡單【原始碼剖析】

HashMap就是這麼簡單【原始碼剖析】


明天要是無意外的話,可能會寫TreeMap,敬請期待哦~~~~

HashMap就是這麼簡單【原始碼剖析】

文章的目錄導航zhongfucheng.bitcron.com/post/shou-j…

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。 謝謝支援了!希望能多介紹給其他有需要的朋友

參考資料:

相關文章