面試官:為什么HashMap 使用的時候指定容量?
回復(fù)架構(gòu)師獲取資源
大家好,我是你們的朋友架構(gòu)君,一個會寫代碼吟詩的架構(gòu)師。
'javajgs.com';
原文:
blog.csdn.net/qq_35387940/article/details/125682065
前言
其實(shí)可以看到我寫了這么久的博客,很少去寫hashMap的東西。
為什么?因?yàn)檫@個東西感覺是java面試必備的,我感覺大家都看到膩了,所以一直沒怎么去寫hashMap相關(guān)的。
ps:之前整理過一個hashmap存值的流程圖,感覺夠了,因?yàn)閜ut過程基本可以把所有核心點(diǎn)都過一遍。
今天為什么我突然要來寫這一篇文章,因?yàn)樽罱诠究匆恍├享?xiàng)目代碼,我才發(fā)現(xiàn)原來其實(shí)很多人都沒用對。
本篇內(nèi)容:
舉例說明 HashMap 使用的時候指定容量 錯誤用法; 源碼走讀,HashMap初始容量的 計(jì)算方式; 源碼走讀擴(kuò)容的點(diǎn); 正確應(yīng)該怎么去用,一定要理解再用; 一些雜談。

為什么要指定容量?
這個原由,都不用說,阿里的java開發(fā)手冊就說的很明白:

其實(shí)核心點(diǎn),就是避免數(shù)據(jù)量慢慢增加,導(dǎo)致反復(fù)觸發(fā)擴(kuò)容,影響性能。
于是乎就很多錯誤的使用方式了(雖熱影響不大):
錯誤理解使用示例 ① :
分頁查詢出來的數(shù)據(jù),需要轉(zhuǎn)換成 Map, 因?yàn)榉猪撌枪潭艘豁撟疃?5條。
所以出現(xiàn)了這個代碼:
Map<String, String> map = new HashMap<>(15);
或者是
Map<String, String> map = new HashMap<>(userPageList.size());
錯誤理解使用示例 ② :
類型type 有 4種, 要放到一個map里面,返回去。
所以出現(xiàn)了這個代碼:
Map<Integer, String> map = new HashMap<>(4);
錯誤理解使用示例 ③:
一個參數(shù)map,里面想放2個參數(shù)。
所以出現(xiàn)了這個代碼:
Map<String, String> map = new HashMap<>(2);
不多舉例,其實(shí)這幾個錯誤示例,都是錯在指定容量的 值上。

默認(rèn) 指定是 傳入 16, 16* 0.75=12 , 所以擴(kuò)容閾值是12 。
說到這里,大家應(yīng)該知道為什么上面是錯誤用法了吧?
比如我們想 存 4個元素到Map, 我們?yōu)榱吮苊夂竺嬗|發(fā)擴(kuò)容影響性能(其實(shí)元素少性能沒多少影響), 就指定了 4 :
Map<Integer, String> map = new HashMap<>(4);
其實(shí)這樣 4x0.75= 3 ,那么如果存放第四個元素的時候,就會觸發(fā)擴(kuò)容

這樣就是違背了我們開始指定 的 4 的最初用意。
實(shí)戰(zhàn)看看這個錯誤使用場景的情況:
同過反射,將capacity屬性的權(quán)限拿到,可以直接打印出來看下capacity的變化,就知道是否觸發(fā)了擴(kuò)容:
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());
}
看下打印效果:

為什么,當(dāng)size =3 ,也就是插入三個元素的時候還沒變。
因?yàn)槲覀兂跏蓟萘恐祩魅氲?4, 4* 0.75 =3. 擴(kuò)容閾值是 3!

當(dāng)插入第四個元素的時候, 就超過了擴(kuò)容閾值,所以觸發(fā)了擴(kuò)容,所以看的最后其實(shí)是進(jìn)行了一次擴(kuò)容,打印出來的capacity是 8.
那么我們應(yīng)該傳多少?

4/0.75 + 1 = 6.3333333
我們指定傳6么?還是傳 7 ?
指定6:

指定7:

指定6,7 都沒區(qū)別好像, 值得慶祝的是,沒有再次觸發(fā)擴(kuò)容。
那么為啥沒區(qū)別呢?

HashMap會轉(zhuǎn)換成大于該capacity 的第一個2的冪作為容量 。
所以傳5,6,7,8 都是 8 ;
傳9,10,11,12,13,14,15,16 都是 16 ;
好了不多啰嗦了, 最后再補(bǔ)一嘴, 默認(rèn)指定容量,其實(shí)就是 內(nèi)存換性能。
所以真正去使用指定容量的時候, 需要考慮:如果我是一個定時任務(wù),允許跑1小時。。。我需要考慮性能么?
或者如果我服務(wù)內(nèi)存很小,我是不是要對內(nèi)存省吃儉用?
這些年小編給你分享過的干貨
2.優(yōu)質(zhì)ERP系統(tǒng)帶進(jìn)銷存財(cái)務(wù)生產(chǎn)功能(附源碼)
3.優(yōu)質(zhì)SpringBoot帶工作流管理項(xiàng)目(附源碼)
5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼)
6.SBoot+Vue可視化大屏拖拽項(xiàng)目(附源碼)

轉(zhuǎn)發(fā)在看就是最大的支持??
