Java中WeakHashMap和HashMap的區(qū)別詳解
WeakHashMap和HashMap的區(qū)別
前面對(duì)HashMap的源碼和WeakHashMap的源碼分別進(jìn)行了分析。在WeakHashMap源碼分析博文中有對(duì)與HashMap區(qū)別的比較,但是不夠具體系統(tǒng)。加上本人看了一些相關(guān)的博文,發(fā)現(xiàn)了一些好的例子來(lái)說(shuō)明這兩者的區(qū)別,因此,就有了這篇博文。
WeakHashMap和HashMap一樣,WeakHashMap也是一個(gè)散列表,它存儲(chǔ)的內(nèi)容也是鍵值對(duì)(key-value)映射,而且鍵和值都可以為null。
不過(guò)WeakHashMap的鍵是“弱鍵”(注:源碼中Entry中的定義是這樣的:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
即Entry實(shí)現(xiàn)了WeakReference類),當(dāng)WeakHashMap某個(gè)鍵不再正常使用時(shí),會(huì)被從WeakHashMap自動(dòng)刪除。
更精確的說(shuō),對(duì)于一個(gè)給定的鍵,其映射的存在并不能阻止垃圾回收器對(duì)該鍵的丟棄,這就使該鍵稱為被終止的,被終止,然后被回收,這樣,這就可以認(rèn)為該鍵值對(duì)應(yīng)該被WeakHashMap刪除。因此,WeakHashMap使用了弱引用作為內(nèi)部數(shù)據(jù)的存儲(chǔ)方案
WeakHashMap可以作為簡(jiǎn)單緩存表的解決方案,當(dāng)系統(tǒng)內(nèi)存不足時(shí),垃圾收集器會(huì)自動(dòng)的清除沒(méi)有在任何其他地方被引用的鍵值對(duì)。如果需要用一張很大的Map作為緩存表時(shí),那么可以考慮使用WeakHashMap。
從源碼的角度,我們來(lái)分析下上面這段話是如何來(lái)工作的??
在WeakHashMap實(shí)現(xiàn)中,借用了ReferenceQueue這個(gè)“監(jiān)聽(tīng)器”來(lái)保存被GC回收的”弱鍵”,然后在每次使用WeakHashMap時(shí),就在WeakHashMap中刪除ReferenceQueue中保存的鍵值對(duì)。即WeakHashMap的實(shí)現(xiàn)是通過(guò)借用 ReferenceQueue這個(gè)“監(jiān)聽(tīng)器”來(lái)優(yōu)雅的實(shí)現(xiàn)自動(dòng)刪除那些引用不可達(dá)的key的。關(guān)于ReferenceQueue會(huì)在下篇博文中進(jìn)行介紹
具體如下:
WeakHashMap是通過(guò)數(shù)組table保存Entry(鍵值對(duì));每個(gè)Entry實(shí)際上就是一個(gè)鏈表來(lái)實(shí)現(xiàn)的。當(dāng)某“弱鍵”不再被其它對(duì)象引用,就會(huì)被GC回收時(shí),這個(gè)“弱鍵”也同時(shí)被添加到ReferenceQueue隊(duì)列中。當(dāng)下一步我們需要操作WeakHashMap時(shí),會(huì)先同步table、queue,table中保存了全部的鍵值對(duì),而queue中保存的是GC回收的鍵值對(duì);同步他們,就是刪除table中被GC回收的鍵值對(duì)。
源碼中完成“刪除”操作的函數(shù)代碼如下:
/** * Expunges stale entries from the table. *翻譯:刪除過(guò)時(shí)的條目,即將ReferenceQueue隊(duì)列中的對(duì)象引用全部在table中給刪除掉 *思路:如何刪除一個(gè)table的節(jié)點(diǎn)e,方法為:首先計(jì)算e的hash值,接著根據(jù)hash值找到其在table的位置,然后遍歷鏈表即可。 */ private void expungeStaleEntries() { for (Object x; (x = queue.poll()) != null; ) { synchronized (queue) { @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) x; int i = indexFor(e.hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; else prev.next = next; // Must not null out e.next; // stale entries may be in use by a HashIterator e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
例子說(shuō)明1
往一個(gè)WeakHashMap中添加大量的元素
上面說(shuō)的可能比較空,比如為什么可以作為緩沖表呀之類,可能看一個(gè)實(shí)際例子之后我們就可以更好的理解上面的兩段話
第一段代碼,就是HashMap的應(yīng)用,往HashMap中存放一系列很大的數(shù)據(jù)。
public class TestHashMap { public static void main(String[] args){ Map<Integer,byte[]> hashMap = new HashMap<Integer,byte[]>(); for(int i=0;i<100000;i++){ hashMap.put(i, new byte[i]); } } }
第二段代碼,就是WeakHashMap的應(yīng)用,往WeakHashMap中存放與上例HashMap相同的數(shù)據(jù)。
public class TestWeakHashMap { public static void main(String[] args){ Map<Integer,byte[]> weakHashMap = new WeakHashMap<Integer,byte[]>(); for(int i=0;i<100000;i++){ weakHashMap.put(i, new byte[i]); } } }
運(yùn)行上面的兩段代碼,發(fā)現(xiàn),第一段代碼是不能正常工作的,會(huì)拋“java.lang.OutOfMemoryError: Java heap space”,而第二段代碼就可以正常工作。
以上就說(shuō)明了,WeakHashMap當(dāng)系統(tǒng)內(nèi)存不足時(shí),垃圾收集器會(huì)自動(dòng)的清除沒(méi)有在任何其他地方被引用的鍵值對(duì),因此可以作為簡(jiǎn)單緩存表的解決方案。而HashMap就沒(méi)有上述功能。
但是,如果WeakHashMap的key在系統(tǒng)內(nèi)持有強(qiáng)引用,那么WeakHashMap就退化為了HashMap,所有的表項(xiàng)都不會(huì)被垃圾回收器回收。
例子說(shuō)明2
一系列的WeakHashMap,往每個(gè)WeakHashMap中只添加一個(gè)大的數(shù)據(jù)
看如下的例子,例子的代碼是,在for循環(huán)中每次都new一個(gè)WeakHashMap對(duì)象,且每個(gè)對(duì)象實(shí)例中只添加一個(gè)key和value都是大的數(shù)組對(duì)象。看會(huì)出現(xiàn)上面現(xiàn)象???
public class TestWeakHashMap3 { public static void main(String[] args){ List<WeakHashMap<Integer[][], Integer[][]>> maps = new ArrayList<WeakHashMap<Integer[][],Integer[][]>>(); int totalNum = 10000; for(int i=0;i<totalNum;i++){ WeakHashMap<Integer[][], Integer[][]> w = new WeakHashMap<Integer[][], Integer[][]>(); w.put(new Integer[1000][1000], new Integer[1000][1000]); maps.add(w); System.gc();//顯示gc System.out.println(i); } } }
上面的運(yùn)行結(jié)果如下:即由于空間不足報(bào)異常錯(cuò)誤。
/* * 運(yùn)行結(jié)果:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space * at com.wrh.testhashmap.TestWeakHashMap3.main(TestWeakHashMap3.java:15) * */
而如下的代碼確能夠正常工作,這兩段代碼的區(qū)別在于下面這段代碼中調(diào)用了WeakHashMap的size()方法。
public class TestWeakHashMap5 { public static void main(String[] args){ List<WeakHashMap<Integer[][], Integer[][]>> maps = new ArrayList<WeakHashMap<Integer[][],Integer[][]>>(); int totalNum = 10000; for(int i=0;i<totalNum;i++){ WeakHashMap<Integer[][], Integer[][]> w = new WeakHashMap<Integer[][], Integer[][]>(); w.put(new Integer[1000][1000], new Integer[1000][1000]); maps.add(w); System.gc(); for(int j=0;j<i;j++){ System.out.println("第"+j+"個(gè)map的大小為:"+maps.get(j).size()); } } } }
可能有人要問(wèn)了,不是說(shuō)WeakHashMap具有會(huì)自動(dòng)進(jìn)行垃圾回收,第一種情況為什么會(huì)報(bào)OOM異常了,第二種情況會(huì)正常工作呢????
首先要說(shuō)明的是,第一段代碼并不是沒(méi)有執(zhí)行GC,而是僅對(duì)WeakHashMap中的key中的Integer數(shù)組進(jìn)行了回收,而value依然保持。我們先來(lái)看如下的例子:將value換成一個(gè)小的對(duì)象Object,就會(huì)證明這一點(diǎn)內(nèi)容。
public static void main(String[] args){ List<WeakHashMap<Integer[][], Object>> maps = new ArrayList<WeakHashMap<Integer[][],Object>>(); int totalNum = 10000; for(int i=0;i<totalNum;i++){ WeakHashMap<Integer[][], Object> w = new WeakHashMap<Integer[][], Object>(); w.put(new Integer[1000][1000], new Object()); maps.add(w); System.gc(); System.out.println(i); } }
上面的代碼運(yùn)行時(shí)沒(méi)有任何問(wèn)題的,這也就證明了key中的Integer數(shù)組確實(shí)被回收了,那為何key中的reference數(shù)據(jù)被GC,卻沒(méi)有觸發(fā)WeakHashMap去做清理整個(gè)key的操作呢??
原因是在于:在進(jìn)行put操作后,雖然GC將WeakReference的key中的Integer數(shù)組回收了,并將事件通過(guò)到了ReferenceQueue,但是后續(xù)卻沒(méi)有相應(yīng)的動(dòng)作去觸發(fā)WeakHashMap來(lái)進(jìn)行處理ReferenceQueue,所以WeakReference包裝的key依然存在在WeakHashMap中,其對(duì)應(yīng)的value也就依然存在。
但是在WeakHashMap中會(huì)刪除那些已經(jīng)被GC的鍵值對(duì)在源碼中是通過(guò)調(diào)用expungeStaleEntries函數(shù)來(lái)完成的,而這個(gè)函數(shù)只在WeakHashMap的put、get、size()等方法中才進(jìn)行了調(diào)用。因此,只有put、get、size()方法來(lái)可以觸發(fā)WeakHashMap來(lái)進(jìn)行處理ReferenceQueue。
以上也就是為什么上面的第二段代碼中調(diào)用下WeakHashMap的size()方法之后就不會(huì)報(bào)異常能正常工作的原因。
到此這篇關(guān)于Java中WeakHashMap和HashMap的區(qū)別詳解的文章就介紹到這了,更多相關(guān)WeakHashMap和HashMap的區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
以Java代碼為例講解設(shè)計(jì)模式中的簡(jiǎn)單工廠模式
簡(jiǎn)單來(lái)說(shuō),工廠模式就是按照需求來(lái)返回一個(gè)類型的對(duì)象,使用工廠模式的意義就是,如果對(duì)象的實(shí)例化與代碼依賴太大的話,不方便進(jìn)行擴(kuò)展和維護(hù),使用工廠的目的就是使對(duì)象的實(shí)例化與主程序代碼就行解耦.來(lái)具體看一下:2016-05-05java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)
這篇文章主要介紹了java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)包括了:配置線程池大小配置,線程池的實(shí)現(xiàn)原理等,需要的朋友可以參考下2023-02-02SpringBoot讀取Resource目錄下文件的四種方式總結(jié)
在Spring?Boot項(xiàng)目中,經(jīng)常需要獲取resources目錄下的文件,這些文件可以包括配置文件、模板文件、靜態(tài)資源等,本文將介紹四種常用的方法來(lái)獲取resources目錄下的文件,需要的朋友可以參考下2023-08-0810個(gè)Java程序員熟悉的面向?qū)ο笤O(shè)計(jì)原則
這篇文章主要為大家詳細(xì)介紹了Java程序員應(yīng)當(dāng)知道的10個(gè)面向?qū)ο笤O(shè)計(jì)原則,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03SpringBoot整合MybatisPlusGernerator實(shí)現(xiàn)逆向工程
在我們寫項(xiàng)目的時(shí)候,我們時(shí)常會(huì)因?yàn)樾枰獎(jiǎng)?chuàng)建很多的項(xiàng)目結(jié)構(gòu)而頭疼,本文主要介紹了SpringBoot整合MybatisPlusGernerator實(shí)現(xiàn)逆向工程,具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05SpringBoot動(dòng)態(tài)表操作服務(wù)的實(shí)現(xiàn)代碼
在現(xiàn)代的應(yīng)用開(kāi)發(fā)中,尤其是在數(shù)據(jù)庫(kù)設(shè)計(jì)不斷變化的情況下,動(dòng)態(tài)操作數(shù)據(jù)庫(kù)表格成為了不可或缺的一部分,在本篇文章中,我們將以一個(gè)典型的動(dòng)態(tài)表操作服務(wù)為例,詳細(xì)介紹如何在 Spring Boot 中使用 JdbcTemplate 實(shí)現(xiàn)動(dòng)態(tài)表管理,需要的朋友可以參考下2025-01-01