Java中WeakHashMap和HashMap的區(qū)別詳解
WeakHashMap和HashMap的區(qū)別
前面對HashMap的源碼和WeakHashMap的源碼分別進行了分析。在WeakHashMap源碼分析博文中有對與HashMap區(qū)別的比較,但是不夠具體系統(tǒng)。加上本人看了一些相關的博文,發(fā)現(xiàn)了一些好的例子來說明這兩者的區(qū)別,因此,就有了這篇博文。
WeakHashMap和HashMap一樣,WeakHashMap也是一個散列表,它存儲的內容也是鍵值對(key-value)映射,而且鍵和值都可以為null。
不過WeakHashMap的鍵是“弱鍵”(注:源碼中Entry中的定義是這樣的:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
即Entry實現(xiàn)了WeakReference類),當WeakHashMap某個鍵不再正常使用時,會被從WeakHashMap自動刪除。
更精確的說,對于一個給定的鍵,其映射的存在并不能阻止垃圾回收器對該鍵的丟棄,這就使該鍵稱為被終止的,被終止,然后被回收,這樣,這就可以認為該鍵值對應該被WeakHashMap刪除。因此,WeakHashMap使用了弱引用作為內部數(shù)據(jù)的存儲方案
WeakHashMap可以作為簡單緩存表的解決方案,當系統(tǒng)內存不足時,垃圾收集器會自動的清除沒有在任何其他地方被引用的鍵值對。如果需要用一張很大的Map作為緩存表時,那么可以考慮使用WeakHashMap。
從源碼的角度,我們來分析下上面這段話是如何來工作的??
在WeakHashMap實現(xiàn)中,借用了ReferenceQueue這個“監(jiān)聽器”來保存被GC回收的”弱鍵”,然后在每次使用WeakHashMap時,就在WeakHashMap中刪除ReferenceQueue中保存的鍵值對。即WeakHashMap的實現(xiàn)是通過借用 ReferenceQueue這個“監(jiān)聽器”來優(yōu)雅的實現(xiàn)自動刪除那些引用不可達的key的。關于ReferenceQueue會在下篇博文中進行介紹
具體如下:
WeakHashMap是通過數(shù)組table保存Entry(鍵值對);每個Entry實際上就是一個鏈表來實現(xiàn)的。當某“弱鍵”不再被其它對象引用,就會被GC回收時,這個“弱鍵”也同時被添加到ReferenceQueue隊列中。當下一步我們需要操作WeakHashMap時,會先同步table、queue,table中保存了全部的鍵值對,而queue中保存的是GC回收的鍵值對;同步他們,就是刪除table中被GC回收的鍵值對。
源碼中完成“刪除”操作的函數(shù)代碼如下:
/** * Expunges stale entries from the table. *翻譯:刪除過時的條目,即將ReferenceQueue隊列中的對象引用全部在table中給刪除掉 *思路:如何刪除一個table的節(jié)點e,方法為:首先計算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; } } } }
例子說明1
往一個WeakHashMap中添加大量的元素
上面說的可能比較空,比如為什么可以作為緩沖表呀之類,可能看一個實際例子之后我們就可以更好的理解上面的兩段話
第一段代碼,就是HashMap的應用,往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的應用,往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]); } } }
運行上面的兩段代碼,發(fā)現(xiàn),第一段代碼是不能正常工作的,會拋“java.lang.OutOfMemoryError: Java heap space”,而第二段代碼就可以正常工作。
以上就說明了,WeakHashMap當系統(tǒng)內存不足時,垃圾收集器會自動的清除沒有在任何其他地方被引用的鍵值對,因此可以作為簡單緩存表的解決方案。而HashMap就沒有上述功能。
但是,如果WeakHashMap的key在系統(tǒng)內持有強引用,那么WeakHashMap就退化為了HashMap,所有的表項都不會被垃圾回收器回收。
例子說明2
一系列的WeakHashMap,往每個WeakHashMap中只添加一個大的數(shù)據(jù)
看如下的例子,例子的代碼是,在for循環(huán)中每次都new一個WeakHashMap對象,且每個對象實例中只添加一個key和value都是大的數(shù)組對象??磿霈F(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); } } }
上面的運行結果如下:即由于空間不足報異常錯誤。
/* * 運行結果:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space * at com.wrh.testhashmap.TestWeakHashMap3.main(TestWeakHashMap3.java:15) * */
而如下的代碼確能夠正常工作,這兩段代碼的區(qū)別在于下面這段代碼中調用了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+"個map的大小為:"+maps.get(j).size()); } } } }
可能有人要問了,不是說WeakHashMap具有會自動進行垃圾回收,第一種情況為什么會報OOM異常了,第二種情況會正常工作呢????
首先要說明的是,第一段代碼并不是沒有執(zhí)行GC,而是僅對WeakHashMap中的key中的Integer數(shù)組進行了回收,而value依然保持。我們先來看如下的例子:將value換成一個小的對象Object,就會證明這一點內容。
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); } }
上面的代碼運行時沒有任何問題的,這也就證明了key中的Integer數(shù)組確實被回收了,那為何key中的reference數(shù)據(jù)被GC,卻沒有觸發(fā)WeakHashMap去做清理整個key的操作呢??
原因是在于:在進行put操作后,雖然GC將WeakReference的key中的Integer數(shù)組回收了,并將事件通過到了ReferenceQueue,但是后續(xù)卻沒有相應的動作去觸發(fā)WeakHashMap來進行處理ReferenceQueue,所以WeakReference包裝的key依然存在在WeakHashMap中,其對應的value也就依然存在。
但是在WeakHashMap中會刪除那些已經被GC的鍵值對在源碼中是通過調用expungeStaleEntries函數(shù)來完成的,而這個函數(shù)只在WeakHashMap的put、get、size()等方法中才進行了調用。因此,只有put、get、size()方法來可以觸發(fā)WeakHashMap來進行處理ReferenceQueue。
以上也就是為什么上面的第二段代碼中調用下WeakHashMap的size()方法之后就不會報異常能正常工作的原因。
到此這篇關于Java中WeakHashMap和HashMap的區(qū)別詳解的文章就介紹到這了,更多相關WeakHashMap和HashMap的區(qū)別內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java 線程池的實現(xiàn)原理、優(yōu)點與風險、以及4種線程池實現(xiàn)
這篇文章主要介紹了java 線程池的實現(xiàn)原理、優(yōu)點與風險、以及4種線程池實現(xiàn)包括了:配置線程池大小配置,線程池的實現(xiàn)原理等,需要的朋友可以參考下2023-02-02SpringBoot讀取Resource目錄下文件的四種方式總結
在Spring?Boot項目中,經常需要獲取resources目錄下的文件,這些文件可以包括配置文件、模板文件、靜態(tài)資源等,本文將介紹四種常用的方法來獲取resources目錄下的文件,需要的朋友可以參考下2023-08-08SpringBoot整合MybatisPlusGernerator實現(xiàn)逆向工程
在我們寫項目的時候,我們時常會因為需要創(chuàng)建很多的項目結構而頭疼,本文主要介紹了SpringBoot整合MybatisPlusGernerator實現(xiàn)逆向工程,具有一定的參考價值,感興趣的可以了解一下2024-05-05SpringBoot動態(tài)表操作服務的實現(xiàn)代碼
在現(xiàn)代的應用開發(fā)中,尤其是在數(shù)據(jù)庫設計不斷變化的情況下,動態(tài)操作數(shù)據(jù)庫表格成為了不可或缺的一部分,在本篇文章中,我們將以一個典型的動態(tài)表操作服務為例,詳細介紹如何在 Spring Boot 中使用 JdbcTemplate 實現(xiàn)動態(tài)表管理,需要的朋友可以參考下2025-01-01