1. 程式人生 > >HashMap中的個數,容量,裝載因子和臨界值

HashMap中的個數,容量,裝載因子和臨界值

本文轉自 

// 個人理解

主要講了四個引數,size大小,capacity容量,loadFactor裝載因子和threshold臨界值

size是指當前hashmap中資料的個數,capacity是hashmap當前最大容納個數

hashmap有自動擴容機制,但是不是到達資料佔滿的時候才擴容的,在達到臨界值*裝載因子時就擴容

比如當前臨界值是16,裝載因子是0.75(通常都是0.75)的時候,當map中個數達到13就會擴容

// 正文

HashMap中重要的成員變數

先來看一下,HashMap中都定義了哪些成員變數。

上面是一張HashMap中主要的成員變數的圖,其中有一個是我們本文主要關注的: size

loadFactorthresholdDEFAULT_LOAD_FACTORDEFAULT_INITIAL_CAPACITY

我們先來簡單解釋一下這些引數的含義,然後再分析他們的作用。

HashMap類中有以下主要成員變數:

  • transient int size; 

    • 記錄了Map中KV對的個數

  • loadFactor 

    • 裝載印子,用來衡量HashMap滿的程度。loadFactor的預設值為0.75f(static final float DEFAULT_LOAD_FACTOR = 0.75f;)。

  • int threshold; 

    • 臨界值,當實際KV個數超過threshold時,HashMap會將容量擴容,threshold=容量*載入因子

  • 除了以上這些重要成員變數外,HashMap中還有一個和他們緊密相關的概念:capacity 

    • 容量,如果不指定,預設容量是16(static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;)

可能看完了你還是有點蒙,size和capacity之間有啥關係?為啥要定義這兩個變數。loadFactor和threshold又是幹啥的?

size 和 capacity

HashMap中的size和capacity之間的區別其實解釋起來也挺簡單的。我們知道,HashMap就像一個“桶”,那麼capacity就是這個桶“當前”最多可以裝多少元素,而size表示這個桶已經裝了多少元素。來看下以下程式碼:

Map<String, String> map = new HashMap<String, String>();
map.put("hollis", "hollischuang");

Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));

Field size = mapType.getDeclaredField("size");
size.setAccessible(true);
System.out.println("size : " + size.get(map));

我們定義了一個新的HashMap,並想其中put了一個元素,然後通過反射的方式列印capacity和size。輸出結果為:capacity : 16、size : 1

預設情況下,一個HashMap的容量(capacity)是16,設計成16的好處我在《全網把Map中的hash()分析的最透徹的文章,別無二家。》中也簡單介紹過,主要是可以使用按位與替代取模來提升hash的效率。

為什麼我剛剛說capacity就是這個桶“當前”最多可以裝多少元素呢?當前怎麼理解呢。其實,HashMap是具有擴容機制的。在一個HashMap第一次初始化的時候,預設情況下他的容量是16,當達到擴容條件的時候,就需要進行擴容了,會從16擴容成32。

我們知道,HashMap的過載的建構函式中,有一個是支援傳入initialCapacity的,那麼我們嘗試著設定一下,看結果如何。

Map<String, String> map = new HashMap<String, String>(1);
map.put("hahaha", "hollischuang");

Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));

Map<String, String> map = new HashMap<String, String>(7);
map.put("hahaha", "hollischuang");

Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));


Map<String, String> map = new HashMap<String, String>(9);
map.put("hahaha", "hollischuang");

Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));

分別執行以上3段程式碼,分別輸出:capacity : 2、capacity : 8、capacity : 16

也就是說,預設情況下HashMap的容量是16,但是,如果使用者通過建構函式指定了一個數字作為容量,那麼Hash會選擇大於該數字的第一個2的冪作為容量。(1->2、7->8、9->16)

這裡有一個小建議:在初始化HashMap的時候,應該儘量指定其大小。尤其是當你已知map中存放的元素個數時。(《阿里巴巴Java開發規約》)

loadFactor 和 threshold

前面我們提到過,HashMap有擴容機制,就是當達到擴容條件時會進行擴容,從16擴容到32、64、128...

那麼,這個擴容條件指的是什麼呢?

其實,HashMap的擴容條件就是當HashMap中的元素個數(size)超過臨界值(threshold)時就會自動擴容。

在HashMap中,threshold = loadFactor * capacity。

loadFactor是裝載因子,表示HashMap滿的程度,預設值為0.75f,設定成0.75有一個好處,那就是0.75正好是3/4,而capacity又是2的冪。所以,兩個數的乘積都是整數(capacity為2也同樣)。

對於一個預設的HashMap來說,預設情況下,當其size大於12(16*0.75)時就會觸發擴容。

驗證程式碼如下:

Map<String, String> map = new HashMap<>();
map.put("hollis1", "hollischuang");
map.put("hollis2", "hollischuang");
map.put("hollis3", "hollischuang");
map.put("hollis4", "hollischuang");
map.put("hollis5", "hollischuang");
map.put("hollis6", "hollischuang");
map.put("hollis7", "hollischuang");
map.put("hollis8", "hollischuang");
map.put("hollis9", "hollischuang");
map.put("hollis10", "hollischuang");
map.put("hollis11", "hollischuang");
map.put("hollis12", "hollischuang");
Class<?> mapType = map.getClass();

Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));

Field size = mapType.getDeclaredField("size");
size.setAccessible(true);
System.out.println("size : " + size.get(map));

Field threshold = mapType.getDeclaredField("threshold");
threshold.setAccessible(true);
System.out.println("threshold : " + threshold.get(map));

Field loadFactor = mapType.getDeclaredField("loadFactor");
loadFactor.setAccessible(true);
System.out.println("loadFactor : " + loadFactor.get(map));

map.put("hollis13", "hollischuang");
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));

Field size = mapType.getDeclaredField("size");
size.setAccessible(true);
System.out.println("size : " + size.get(map));

Field threshold = mapType.getDeclaredField("threshold");
threshold.setAccessible(true);
System.out.println("threshold : " + threshold.get(map));

Field loadFactor = mapType.getDeclaredField("loadFactor");
loadFactor.setAccessible(true);
System.out.println("loadFactor : " + loadFactor.get(map));

輸出結果:

capacity : 16
size : 12
threshold : 12
loadFactor : 0.75

capacity : 32
size : 13
threshold : 24
loadFactor : 0.75

當HashMap中的元素個數達到13的時候,capacity就從16擴容到32了。

HashMap中還提供了一個支援傳入initialCapacity,loadFactor兩個引數的方法,來初始化容量和裝載因子。不過,一般不建議修改loadFactor的值。

總結

HashMap中size表示當前共有多少個KV對,capacity表示當前HashMap的容量是多少,預設值是16,每次擴容都是成倍的。loadFactor是裝載因子,當Map中元素個數超過loadFactor* capacity的值時,會觸發擴容。loadFactor* capacity可以用threshold表示。

PS:文中分析基於JDK1.8.0_73