HashMap源碼分析(一):JDK源碼分析系列

正文開始 注:JDK版本爲1.8
java

HashMap1.8和1.8以前的源碼差異很大api


  • 目錄
    • 簡介
      • 數據結構
    • 類結構
    • 屬性
    • 構造方法
    • 增長
    • 刪除
    • 修改
    • 總結

1.HashMap簡介

HashMap基於哈希表的Map接口實現,是以key-value存儲形式存在。(除了不一樣步和容許使用 null 以外,HashMap 類與 Hashtable 大體相同。)

HashMap 的實現不是同步的,這意味着它不是線程安全的。它的key、value均可覺得null。此外,HashMap中的映射不是有序的。在 JDK1.8 中,HashMap 是由 數組+鏈表+紅黑樹構成,新增了紅黑樹做爲底層數據結構,結構變得複雜了,可是效率也變的更高效。數組

1.2 HashMap數據結構

在 JDK1.8 中,HashMap 是由 數組+鏈表+紅黑樹構成,新增了紅黑樹做爲底層數據結構,結構變得複雜了,可是效率也變的更高效。當一個值中要存儲到Map的時候會根據Key的值來計算出他的緩存

hash,經過哈希來確認到數組的位置,若是發生哈希碰撞就以鏈表的形式存儲在Object源碼分析中解釋過,可是這樣若是鏈表過長來的話,HashMap會把這個鏈表轉換成紅黑樹來存儲。安全

來看依一下HashMap的存儲結構數據結構

源Google搜索

可是這樣的話問題來了,HashMap爲何要使用紅黑樹呢,這樣結構的話不是更麻煩了嗎??框架

這個問題我也沒有想過,其實不少在看的時候只會在意紅黑樹的實現而忽略到了爲何要使用的這個問題,我也是在寫本文的時候突發疑惑。參考了網上的例子,同時也解釋了爲何閥值爲8:源碼分析

由於Map中桶的元素初始化是鏈表保存的,其查找性能是O(n),而樹結構能將查找性能提高到O(log(n))。當鏈表長度很小的時候,即便遍歷,速度也很是快,可是當鏈表長度不斷變長,確定會對查詢性能有必定的影響,因此才須要轉成樹。至於爲何閾值是8,我想,去源碼中找尋答案應該是最可靠的途徑。性能

參考地址:https://dwz.cn/nPFXmXwJ優化

2.類結構

咱們來看一下類結構

在閱讀源碼的時候一直有個問題很困惑就是HashMap已經繼承了AbstractMap而AbstractMap類實現了Map接口,那爲何HashMap還要在實現Map接口呢?一樣在ArrayList中LinkedList中都是這種結構。

據 java 集合框架的創始人Josh Bloch描述,這樣的寫法是一個失誤。在java集合框架中,相似這樣的寫法不少,最開始寫java集合框架的時候,他認爲這樣寫,在某些地方多是有價值的,直到他意識到錯了。顯然的,JDK的維護者,後來不認爲這個小小的失誤值得去修改,因此就這樣存在下來了。

  • Cloneable 空接口,表示能夠克隆
  • Serializable 序列化
  • AbstractMap 提供Map實現接口

3.屬性

初始化容量(必須是二的n次冪)

集合最大容量(必須是二的冪)

負載因子,默認的0.75

當鏈表的值超過8則會轉紅黑樹(1.8新增)

當鏈表的值小於6則會從紅黑樹轉回鏈表

當Map裏面的數量超過這個值時,表中的桶才能進行樹形化 ,不然桶內元素太多時會擴容,而不是樹形化 爲了不進行擴容、樹形化選擇的衝突,這個值不能小於 4 * TREEIFY_THRESHOLD

table用來初始化(必須是二的n次冪)

用來存放緩存

HashMap中存儲的數量

用來記錄HashMap的修改次數

用來調整大小下一個容量的值計算方式爲(容量*負載因子)

哈希表的加載因子

重點屬性

  • table 在JDK1.8中咱們瞭解到HashMap是由數組加鏈表加紅黑樹來組成的結構其中table就是HashMap中的數組
  • Size 爲HashMap中K-V的實時數量
  • loadFactor 加載因子,是用來衡量 HashMap 滿的程度,計算HashMap的實時加載因子的方法爲:size/capacity,而不是佔用桶的數量去除以capacity。capacity 是桶的數量,也就是 table 的長度length。
  • threshold 計算公式:capacity * loadFactor。這個值是當前已佔用數組長度的最大值。過這個數目就從新resize(擴容),擴容後的 HashMap 容量是以前容量的兩倍

4.構造方法

開始看構造方法。

4.1 HashMap()

構造一個空的 HashMap ,默認初始容量(16)和默認負載因子(0.75)。

4.2 HashMap(int initialCapacity)

構造一個空的 HashMap具備指定的初始容量和默認負載因子(0.75)。

4.3 HashMap(int initialCapacity, float loadFactor)

構造一個空的 HashMap具備指定的初始容量和負載因子。咱們來分析一下。

最後調用了tableSizeFor,來看一下方法實現:

5.增長

如今咱們開始分析put()方法

咱們能夠看到put調用的是putVal來進行數據插入,可是要注意到key在這裏執行了一下hash()方法,來看一下Hash方法是如何實現的。

從上面能夠得知HashMap是支持Key爲空的,而HashTable是直接用過Key來獲取HashCode因此key爲空會拋異常其實上面就已經解釋了爲何HashMap的長度爲何要是2的冪由於HashMap 使用的方法很巧妙,它經過 hash & (table.length -1)來獲得該對象的保存位,前面說過 HashMap 底層數組的長度老是2的n次方,這是HashMap在速度上的優化。當 length 老是2的n次方時,hash & (length-1)運算等價於對 length 取模,也就是 hash%length,可是&比%具備更高的效率。好比 n % 32 = n & (32 -1)。

如今看putVal()方法,看看它到底作了什麼。

主要參數:

  • hash key的hash值
  • key 原始Key
  • value 要存放的值
  • onlyIfAbsent 若是true表明不更改現有的值
  • evict 若是爲false表示table爲建立狀態

完整源碼分析,放圖片的話會太長了,因此就截取了一下分爲兩部。

暫時分析到添加 ,首發亂敲代碼公衆號

相關文章
相關標籤/搜索