天天看點

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

前言

其實可以看到我寫了這麼久的部落格,很少去寫hashMap的東西。

為什麼? 因為這個東西感覺是java面試必備的,我感覺大家都看到膩了,是以一直沒怎麼去寫hashMap相關的。

ps: 之前整理過一個hashmap存值的流程圖,感覺夠了,因為put過程基本可以把所有核心點都過一遍。​​JAVA jdk1.8 Hash

今天為什麼我突然要來寫這一篇文章,因為最近在公司看一些老項目代碼,我才發現原來其實很多人都沒用對。

本篇内容:

舉例說明 HashMap 使用的時候指定容量 錯誤用法;

源碼走讀,HashMap初始容量的 計算方式;

源碼走讀擴容的點;

正确應該怎麼去用,一定要了解再用;

一些雜談。

正文

不開玩笑,真的都知道指定容量,但是有些用對了,有些沒用對。

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

為什麼要指定容量?

這個原由,都不用說,阿裡的java開發手冊就說的很明白:

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

 其實核心點,就是避免資料量慢慢增加,導緻反複觸發擴容,影響性能。

于是乎就很多錯誤的使用方式了(雖熱影響不大):

錯誤了解使用示例 ① :

分頁查詢出來的資料,需要轉換成 Map, 因為分頁是固定了一頁最多15條。

是以出現了這個代碼:

Map<String, String> map = new HashMap<>(15);

或者是 

Map<String, String> map = new HashMap<>(userPageList.size());

錯誤了解使用示例 ② :

類型type 有 4種, 要放到一個map裡面,傳回去。

是以出現了這個代碼:

Map<Integer, String> map = new HashMap<>(4);

錯誤了解使用示例 ③:

一個參數map,裡面想放2個參數。

是以出現了這個代碼:

Map<String, String> map = new HashMap<>(2);

不多舉例,其實這幾個錯誤示例,都是錯在指定容量的 值上。

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

預設 指定是 傳入 16, 16* 0.75=12 , 是以擴容門檻值是12 。

說到這裡,大家應該知道為什麼上面是錯誤用法了吧?

比如我們想 存 4個元素到Map, 我們為了避免後面觸發擴容影響性能(其實元素少性能沒多少影響), 就指定了 4 :

Map<Integer, String> map = new HashMap<>(4);

其實這樣 4x0.75= 3 ,那麼如果存放第四個元素的時候,就會觸發擴容

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

 這樣就是違背了我們開始指定 的 4 的最初用意。

實戰看看這個錯誤使用場景的情況:

同過反射,将capacity屬性的權限拿到,可以直接列印出來看下capacity的變化,就知道是否觸發了擴容:

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Map<String, String> map = new HashMap<>(4);
        Class<?> mapType = map.getClass();
        Method capacity = mapType.getDeclaredMethod("capacity");
        capacity.setAccessible(true);

        map.put("1", "第一個元素插入");
        System.out.println("capacity : " + capacity.invoke(map) + "    size : " + map.size());
        map.put("2", "第二個元素插入");
        System.out.println("capacity : " + capacity.invoke(map) + "    size : " + map.size());
        map.put("3", "第三個元素插入");
        System.out.println("capacity : " + capacity.invoke(map) + "    size : " + map.size());
        map.put("4", "第四個元素插入");
        System.out.println("capacity : " + capacity.invoke(map) + "    size : " + map.size());

    }      

看下列印效果: 

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

為什麼,當size =3 ,也就是插入三個元素的時候還沒變。 

因為我們初始化容量值傳入的 4,  4* 0.75 =3. 擴容門檻值是 3!  

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

當插入第四個元素的時候, 就超過了擴容門檻值,是以觸發了擴容,是以看的最後其實是進行了一次擴容,列印出來的capacity是 8.

那麼我們應該傳多少?

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

4/0.75  + 1 = 6.3333333

我們指定傳6麼? 還是傳 7 ?

指定6:

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

指定7:

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

指定6,7 都沒差別好像, 值得慶祝的是,沒有再次觸發擴容。

那麼為啥沒差別呢?

HashMap 使用的時候指定容量?你真的用明白了嗎?(值得一閱)

HashMap會轉換成大于該capacity 的第一個2的幂作為容量 。

是以傳5,6,7,8 都是 8 ;

傳9,10,11,12,13,14,15,16 都是 16 ;

好了不多啰嗦了, 最後再補一嘴, 預設指定容量,其實就是 記憶體換性能。