1. 程式人生 > >HashMap的擴容機制------resize()

HashMap的擴容機制------resize()

一:首先要知道HashMap什麼時候擴容
當元素向HashMap容器中新增元素的時候,會判斷當前元素的個數,如果當前元素的個數大於等於閾值時,即當前陣列table的長度*載入因子就要進行自動擴容。

由於HashMap的底層資料結構是“連結串列雜湊”,即陣列和連結串列的組合,而陣列是無法自動擴容的,所以只能是換一個更大的陣列去裝填以前的元素和將要新增的新元素

分析resize()方法的原始碼可以發現:在jdk1.7以前,resize()方法傳入的引數是新陣列的容量,HashMap也不是無限能擴容的,1:方法中會首先判斷擴容前的舊陣列容量是否已經達到最大即2^30了,如果達到了就修改閾值為int的最大取值,這樣以後就不會擴容了。
2:初始化一個新Entry陣列;
3:計算rehash,判斷擴容的時候是否需要重新計算hash值

,將此值作為引數傳入到transfer方法中;這個是和jdk1.8不同的一點,待會看1.8
4:通過transfer方法將舊陣列中的元素複製到新陣列,在這個方法中進行了包括釋放就的Entry中的物件引用,該過程中如果需要重新計算hash值就重新計算,然後根據indexfor()方法計算索引值。而索引值的計算方法為{ return h & (length-1) ;}即hashcode計算出的hash值和陣列長度進行與運算。。。。。jdk1.7中重新插入到新陣列的元素,如果原來一條鏈上的元素又被分配到同一條鏈上那麼他們的順序會發生倒置這個和1.8也不一樣

二:jdk1.8的優化
1:resize方法原始碼融入了紅黑樹,本質和1.7區別不大,但是在插入元素的時候迴圈舊陣列內的元素時會進行判斷,如果是普通節點直接和1.7一樣放置;如果是紅黑樹結構,就呼叫split()方法進行拆分放置;如果是連結串列,則採用下面2中要分析的方式!!!!
2:在經過一次容量判斷是否大於最大值之後在進行擴容,使用的擴容方法是2次冪的擴充套件,**所以元素要麼在原來的位置,要麼在原位置在移動2次冪的位置,**如下圖:圖(a)表示擴容前的key1和key2兩種key確定索引位置的示例,圖(b)表示擴容後key1和key2兩種key確定索引位置的示例,其中hash1是key1對應的雜湊與高位運算結果。
在這裡插入圖片描述

元素在重新計算hash之後,因為n變為2倍,那麼n-1的mask範圍在高位多1bit(紅色),因此新的index就會發生這樣的變化:
在這裡插入圖片描述

因此,其實我們在擴容的時候不需要像jdk1.7那樣重新計算hash,只要看看原hash值新增的那個bit位是1還是0就好了,是0的話索引沒有變,是1的話索引變成“原索引+oldCap(就陣列大小)”,下圖位resize()方法示意圖:
在這裡插入圖片描述

這個設計確實非常的巧妙,既省去了重新計算hash值的時間,也就是說1.8不用重新計算hash值而且同時,由於新增的1bit是0還是1可以認為是隨機的,因此resize的過程,均勻的把之前的衝突的節點分散到新的bucket了。這一塊就是JDK1.8新增的優化點。有一點注意區別,JDK1.7中rehash的時候,舊連結串列遷移新連結串列的時候,如果在新表的陣列索引位置相同,則連結串列元素會倒置,因為他採用的是頭插法,先拿出舊連結串列頭元素。但是從上圖可以看出**,JDK1.8不會倒置,採用的尾插法。**