詳解Java?Map中三個(gè)冷門容器的使用
概述
本篇文章主要講解下Map家族中3個(gè)相對(duì)冷門的容器,分別是WeakHashMap、EnumMap、IdentityHashMap, 想必大家在平時(shí)的工作中也很少用到,或者壓根不知道他們的特性以及適用場(chǎng)景,本篇文章就帶你一探究竟。
WeakHashMap
介紹
WeakHashMap稱為弱三列映射,實(shí)現(xiàn)了Map接口,具有如下特性:
- WeakHashMap中的entry是一個(gè)弱引用,當(dāng)除了自身有對(duì)key的引用外,此key沒有其他引用,那么GC之后此map會(huì)自動(dòng)丟棄此值。
- 不是線程安全的
- 可以存儲(chǔ)null
演示案例
public static void main(String[] args) { String a = new String("a"); String b = new String("b"); Map weakmap = new WeakHashMap(); weakmap.put(a, "aaa"); weakmap.put(b, "bbb"); a = null; b = null; // 進(jìn)行g(shù)c System.gc(); Iterator j = weakmap.entrySet().iterator(); while (j.hasNext()) { Map.Entry en = (Map.Entry) j.next(); System.out.println("weakmap:" + en.getKey() + ":" + en.getValue()); } }
運(yùn)行結(jié)果:
已經(jīng)被gc回收了。
原理實(shí)現(xiàn)
從這里我們可以看到其內(nèi)部的Entry繼承了WeakReference,也就是弱引用,所以就具有了弱引用的特點(diǎn)。
弱引用的特點(diǎn)是在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。
WeakReference中有個(gè)成員變量ReferenceQueue,他的作用是GC會(huì)清理掉對(duì)象之后,引用對(duì)象會(huì)被放到ReferenceQueue中,然后遍歷這個(gè)queue進(jìn)行刪除即可Entry。WeakHashMap內(nèi)部有一個(gè)expungeStaleEntries函數(shù),在這個(gè)函數(shù)內(nèi)部實(shí)現(xiàn)移除其內(nèi)部不用的entry從而達(dá)到的自動(dòng)釋放內(nèi)存的目的。因此我們每次訪問WeakHashMap的時(shí)候,都會(huì)調(diào)用這個(gè)expungeStaleEntries函數(shù)清理一遍。
使用場(chǎng)景
在如今的并發(fā)泛濫的大環(huán)境下,大家應(yīng)該都用過緩存,緩存都是放在內(nèi)存中的,而內(nèi)存幾乎是計(jì)算機(jī)中最寶貴也是最稀缺的資源,所以需要謹(jǐn)慎的使用,不然很容易就出現(xiàn) OOM。緩存的主要作用是為了更快的處理業(yè)務(wù)、降低服務(wù)器的壓力,那么就要保證緩存命中率,這里假設(shè)整個(gè)緩存是一個(gè) key-value 結(jié)構(gòu)的(以鍵值對(duì)緩存為例),HashMap 作為強(qiáng)引用對(duì)象在沒有主動(dòng)將 key 刪除時(shí)是不會(huì)被 JVM 回收的,這樣 HashMap 中的對(duì)象就會(huì)越積越多直到 OOM 錯(cuò)誤;那么如何做到既讓緩存的命中率高又不占用那么多的內(nèi)存,這里就可以采用 WeakHashMap,當(dāng)然不會(huì)有 HashMap 100% 的命中率(假設(shè)內(nèi)存足夠),但是在保證程序正常的前提下更好的實(shí)現(xiàn)了緩存這套解決方案。
EnumMap
介紹
用于枚舉類型鍵的專用Map實(shí)現(xiàn)。枚舉映射中的所有鍵必須來自創(chuàng)建映射時(shí)顯式或隱式指定的單個(gè)枚舉類型。
相對(duì)于HashMap中枚舉作為key, EnumMap內(nèi)部以一個(gè)非常緊湊的數(shù)組存儲(chǔ)value,并且根據(jù)enum類型的key直接定位到內(nèi)部數(shù)組的索引,并不需要計(jì)算hashCode(),不但效率最高,而且沒有額外的空間浪費(fèi)。
- 不是線程安全的
- 可以存放null值
演示案例
public static void main(String[] args) { // 構(gòu)造函數(shù)傳入類型 Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class); map.put(DayOfWeek.MONDAY, "星期一"); map.put(DayOfWeek.TUESDAY, "星期二"); map.put(DayOfWeek.WEDNESDAY, "星期三"); map.put(DayOfWeek.THURSDAY, "星期四"); map.put(DayOfWeek.FRIDAY, "星期五"); map.put(DayOfWeek.SATURDAY, "星期六"); map.put(DayOfWeek.SUNDAY, "星期日"); System.out.println(map); System.out.println(map.get(DayOfWeek.MONDAY)); } enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
原理實(shí)現(xiàn)
put方法源碼如下:
public V put(K key, V value) { // 對(duì)枚舉類型進(jìn)行檢查,看key和構(gòu)造函數(shù)傳入的class類型是否一致 typeCheck(key); // 枚舉的順序 int index = key.ordinal(); // 原來位置的值 Object oldValue = vals[index]; // 設(shè)置值 vals[index] = maskNull(value); if (oldValue == null) size++; return unmaskNull(oldValue); }
通過put源碼發(fā)現(xiàn)是通過數(shù)組的方式實(shí)現(xiàn)存儲(chǔ),而且也不需要進(jìn)行擴(kuò)容。
使用場(chǎng)景
如果項(xiàng)目中遇到針對(duì)枚舉作為key的映射容器,可以優(yōu)先選擇EnumMap。
IdentityHashMap
介紹
該類使用散列表實(shí)現(xiàn)Map接口,在比較鍵(和值)時(shí)使用引用相等代替對(duì)象相等。換句話說,在一個(gè)IdentityHashMap中,當(dāng)且僅當(dāng)(k1k2)兩個(gè)鍵k1和k2被認(rèn)為是相等的。(在正常的Map實(shí)現(xiàn)(如HashMap)兩個(gè)鍵k1和k2被認(rèn)為是相等的,當(dāng)且僅當(dāng)(k1null ?k2 = = null: k1.equals (k2)))。
- 不是線程安全的
- 無序
- key不可以是null
演示案例
public static void main(String[] args) { // hashMap Map<Integer, String> hashMap = new HashMap<>(); // identityHashMap Map<Integer, String> identityHashMap = new IdentityHashMap<>(); hashMap.put(new Integer(200), "a"); hashMap.put(new Integer(200), "b"); identityHashMap.put(new Integer(200), "a"); identityHashMap.put(new Integer(200), "b"); //遍歷hashmap System.out.println("hashmap 結(jié)果:"); hashMap.forEach((key, value) -> { System.out.println("key = " + key + ", value = " + value); }); //遍歷hashmap System.out.println("identityHashMap 結(jié)果:"); identityHashMap.forEach((key, value) -> { System.out.println("key = " + key + ", value = " + value); }); }
運(yùn)行結(jié)果:
原理實(shí)現(xiàn)
IdentityHashMap底層的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,我們關(guān)注下put方法:
調(diào)用hash方法,獲取key在table的位置index,然后進(jìn)行賦值操作,也是分成了3種情況:
1.item == k,找到了對(duì)應(yīng)的key,value存在key右相鄰的位置,對(duì)tab[i + 1]進(jìn)行更新,并返回原來的值;
2.item == null,表示table中沒有對(duì)應(yīng)的key值,跳出for循環(huán),執(zhí)行tab[i] = k和tab[i + 1] = value進(jìn)行新key的插入操作。個(gè)人覺得這里的擴(kuò)容時(shí)機(jī)選擇的不太好,好不容易找到的更新位置,因?yàn)閿U(kuò)容給整沒了,還得再次重新計(jì)算,可以和HashMap一樣,在更新后再擴(kuò)容。
3.item != null && item != key,表示hash沖突發(fā)生,調(diào)用nextKeyIndex獲取處理沖突后的index位置,然后重復(fù)上面的過程。
我們?cè)賮砜聪耯ash方法:
IdentityHashMap中獲取hash值采用的System.identityHashCode方法,在不重寫Object.hashCode方法時(shí),System.identityHashCode和Object.hashCode返回的值相同,相當(dāng)于對(duì)象的唯一的HashCode。System.identityHashCode(null)始終返回0, 無論是否重寫Object.hashCode,都不影響System.identityHashCode的執(zhí)行結(jié)果。
使用場(chǎng)景
當(dāng)我們必須使用地址相等來判斷值相等的場(chǎng)合,以及我們確定只要其地址不相等,則其equals方法的結(jié)果也必定不相等的場(chǎng)合。
到此這篇關(guān)于詳解Java Map中三個(gè)冷門容器的使用的文章就介紹到這了,更多相關(guān)Java Map容器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA8 STREAM COLLECT GROUPBY分組實(shí)例解析
這篇文章主要介紹了JAVA8 STREAM COLLECT GROUPBY分組實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案
今天小編就為大家分享一篇關(guān)于Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02