原始碼分析為什麼HashMap的table長度一定是2的整次冪

散居閒人發表於2020-12-04

我們今天從原始碼入手分析一下為什麼HashMap的陣列table長度一定是2的整次冪。

首先我們先從構造方法來分析HashMap的初始化長度:

HashMap中有4個構造方法
在這裡插入圖片描述
我們來看看他們分別都是什麼

1 . HashMap();

在這裡插入圖片描述
無參的建構函式,從註釋中我們可以看出,如果我們不指定初始長度,那麼陣列的初始長度就是預設的長度,是一個靜態常量DEFAULT_LOAD_FACTOR = 16;
emmm確實是2的4次冪,但是不能說明任何問題;我們繼續看

2 . HashMap(int initialCapacity)

在這裡插入圖片描述
誒,我們找到了了一個有一個引數的構造方法,並且這個引數就是用來指定我們陣列的初始化長度的,那麼我們如果給它一個不是2的整次冪的值,它是咋辦的呢,因為這個方法呼叫了另外一個方法,我們想知道還得看一下這個裡面的構造方法,也就是第三個構造方法。

3 . HashMap (int initialCapacity, float loadFactor);

在這裡插入圖片描述
終於有了比較有價值的程式碼了,我們拋開前面的判斷,我們最後的目光就落在了最後一個方法
this.threshold = tableSizeFor(initialCapacity);
注意:雖然此處是對threshold賦值,但是後面table初始化時會將值賦給table長度,並且重新對threshold進行賦值為 capacity * loadFactory,這裡不多贅述。

這個方法最後對我們傳入的初始長度值進行了加工。再來看一下這個方法
在這裡插入圖片描述
其實從註釋我們就已經看的很清楚了,方法返回一個兩倍大小的目標容量的冪,誒
但是我們還是看一下程式碼的實現,就是“與”運算一連串的無符號右位移,我們隨便代入一個值 14
n = 14 - 1 = 13

13的2進製為
0000 0000 0000 0000 0000 0000 0000 1101
計算n = n | n >>> 1
0000 0000 0000 0000 0000 0000 0000 1101
				|
0000 0000 0000 0000 0000 0000 0000 0110	
結果為
0000 0000 0000 0000 0000 0000 0000 1111

然後用這個結果 = n繼續與它右位移後的值進行“或”運算,但是想在我們就發現,不管後面再計算幾次,結果已經不會變了,永遠是1111,所以最後n的結果為15。
我們再代入到最後的三目運算子中,n小於最大的容量,所以最終n的結果為16

誒,有點意思,但其實我們發現,這個運算是返回一個大於它且是最近的2的整次冪的數。
有點繞,我們換個角度
在這裡插入圖片描述
分成一個個的小區間,比如 16 < n < 32 的數就會返回32
因為 (2的整次冪 - 1) 的2進位制有效位一定都是1

4 . HashMap(Map<? extends K, ? extends V> m)

在這裡插入圖片描述
第四個構造方法是將制定的Map物件例項化成一個HashMap物件,與我們討論的問題關係不大。

至此,我們已經明白了構造方法的初始值處理。但是我們知道,陣列的長度是會改變的,當我們再map中儲存的元素超過threshold時,就會觸發擴容機制,我們的陣列長度進行擴容,我們來看一下擴容方法
在這裡插入圖片描述
resize()方法我們這裡就不過多的分析,我們只看table陣列長度變化的時候,我們從原始碼中可以清晰的看到,新陣列長度是舊陣列長度的一倍。是2倍的擴容機制。

至此我們就從原始碼中瞭解了為什麼HashMap中的陣列長度一定是2的整次冪了。如果有不對的地方希望大家指正!

還有一些問題,比如陣列長度是2的整次冪的好處,以及HashMap的原始碼分析請看我的其他文章。

相關文章