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 。
兩條鏈表的連接過程:
在向表中連接的時候最后一個節點的下一個節點做空。
總結:
- 在對 HashMap 進行擴容時,閥值會變為原來的兩倍;
- 在對HashMap進行擴容的時候,HashMap的容量會變為原來的兩倍;
- 擴容是一個特別耗性能的操作,所以當程序員在使用HashMap的時候,估算map的大小,初始化的時候給一個大致的數值,避免map進行頻繁的擴容。
- 負載因子是可以修改的,也可以大於1,但是建議不要輕易修改,除非情況非常特殊。