HashMap擴容機制


1.什么是resize:

resize就是重新計算容量;當我們不斷的向HashMap對象里不停的添加元素時,HashMap對象內部的數組就會出現無法裝載更多的元素,這是對象就需要擴大數組的長度,以便能裝入更多的元素;當然Java里的數組是無法自動擴容的,方法是使用一個新的數組代替已有的容量小的數組;就像我們用一個小桶裝水,如果想裝更多的水,就得換大水桶。

2.什么時候需要resize():

當向容器添加元素的時候,會判斷當前容器的元素個數,如果大於等於閾值—即當前數組的長度乘以加載因子的值的時候,就要自動擴容。

擴容:(源碼 661-662)

這里寫圖片描述

計算閥值:

1.第一次創建Hash表時:

這里寫圖片描述

2.對HashMap進行擴容時:

這里寫圖片描述

3.源碼分析:

final Node<K,V>[] resize() { //保存舊的 Hash 數組 Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { //超過最大容量,不再進行擴充 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //容量沒有超過最大值,容量變為原來的兩倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //閥值變為原來的兩倍 newThr = oldThr << 1; } else if (oldThr > 0) newCap = oldThr; else { //閥值和容量使用默認值 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { //計算新的閥值 float ft = (float)newCap * loadFactor; //閥值沒有超過最大閥值,設置新的閥值 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) //創建新的 Hash 表 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; //遍歷舊的 Hash 表 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { //釋放空間 oldTab[j] = null; //當前節點不是以鏈表的形式存在 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //紅黑樹的形式,略過 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { //以鏈表形式存在的節點; //這一段還是看下面的圖解吧,搞了好久才懂得 ^_^ Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { //最后一個節點的下一個節點做空 loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { //最后一個節點的下一個節點做空 hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

以鏈表形式存在的節點:

存在兩個數他們的 Hash 值分別為:5,21 二進制形式分別為(0101,10101)。

若沒有進行擴容時容量為 16,進行擴容之后的容量為 32

坐標點的計算(計算規則 :e.hash & (newCap - 1)):

沒有進行擴容時:

這里寫圖片描述

可以看到兩個Hash值所計算的坐標是相同的。

進行擴容之后:

這里寫圖片描述

可以看出經過擴容之后,兩次計算的坐標出現了不同,但是第二個坐標點增加了 oldCap 個長度。

再看看 e.hash & oldCap 所計算出的結果:

這里寫圖片描述

可以看到當 e.hash & oldCap == 0 是,原來的坐標沒有發生變化,e.hash & oldCap != 0 在原來坐標的前提下增加 oldCap 。

兩條鏈表的連接過程:

這里寫圖片描述

這里寫圖片描述

這里寫圖片描述

在向表中連接的時候最后一個節點的下一個節點做空。

總結:

  1. 在對 HashMap 進行擴容時,閥值會變為原來的兩倍;
  2. 在對HashMap進行擴容的時候,HashMap的容量會變為原來的兩倍;
  3. 擴容是一個特別耗性能的操作,所以當程序員在使用HashMap的時候,估算map的大小,初始化的時候給一個大致的數值,避免map進行頻繁的擴容。
  4. 負載因子是可以修改的,也可以大於1,但是建議不要輕易修改,除非情況非常特殊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM