Java中WeakHashMap和HashMap的区别详解
作者:HelloWorld_EE
WeakHashMap和HashMap的区别
前面对HashMap的源码和WeakHashMap的源码分别进行了分析。在WeakHashMap源码分析博文中有对与HashMap区别的比较,但是不够具体系统。加上本人看了一些相关的博文,发现了一些好的例子来说明这两者的区别,因此,就有了这篇博文。
WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。
不过WeakHashMap的键是“弱键”(注:源码中Entry中的定义是这样的:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
即Entry实现了WeakReference类),当WeakHashMap某个键不再正常使用时,会被从WeakHashMap自动删除。
更精确的说,对于一个给定的键,其映射的存在并不能阻止垃圾回收器对该键的丢弃,这就使该键称为被终止的,被终止,然后被回收,这样,这就可以认为该键值对应该被WeakHashMap删除。因此,WeakHashMap使用了弱引用作为内部数据的存储方案
WeakHashMap可以作为简单缓存表的解决方案,当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对。如果需要用一张很大的Map作为缓存表时,那么可以考虑使用WeakHashMap。
从源码的角度,我们来分析下上面这段话是如何来工作的??
在WeakHashMap实现中,借用了ReferenceQueue这个“监听器”来保存被GC回收的”弱键”,然后在每次使用WeakHashMap时,就在WeakHashMap中删除ReferenceQueue中保存的键值对。即WeakHashMap的实现是通过借用 ReferenceQueue这个“监听器”来优雅的实现自动删除那些引用不可达的key的。关于ReferenceQueue会在下篇博文中进行介绍
具体如下:
WeakHashMap是通过数组table保存Entry(键值对);每个Entry实际上就是一个链表来实现的。当某“弱键”不再被其它对象引用,就会被GC回收时,这个“弱键”也同时被添加到ReferenceQueue队列中。当下一步我们需要操作WeakHashMap时,会先同步table、queue,table中保存了全部的键值对,而queue中保存的是GC回收的键值对;同步他们,就是删除table中被GC回收的键值对。
源码中完成“删除”操作的函数代码如下:
/** * Expunges stale entries from the table. *翻译:删除过时的条目,即将ReferenceQueue队列中的对象引用全部在table中给删除掉 *思路:如何删除一个table的节点e,方法为:首先计算e的hash值,接着根据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中存放一系列很大的数据。
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相同的数据。
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]); } } }
运行上面的两段代码,发现,第一段代码是不能正常工作的,会抛“java.lang.OutOfMemoryError: Java heap space”,而第二段代码就可以正常工作。
以上就说明了,WeakHashMap当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对,因此可以作为简单缓存表的解决方案。而HashMap就没有上述功能。
但是,如果WeakHashMap的key在系统内持有强引用,那么WeakHashMap就退化为了HashMap,所有的表项都不会被垃圾回收器回收。
例子说明2
一系列的WeakHashMap,往每个WeakHashMap中只添加一个大的数据
看如下的例子,例子的代码是,在for循环中每次都new一个WeakHashMap对象,且每个对象实例中只添加一个key和value都是大的数组对象。看会出现上面现象???
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) * */
而如下的代码确能够正常工作,这两段代码的区别在于下面这段代码中调用了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异常了,第二种情况会正常工作呢????
首先要说明的是,第一段代码并不是没有执行GC,而是仅对WeakHashMap中的key中的Integer数组进行了回收,而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数组确实被回收了,那为何key中的reference数据被GC,却没有触发WeakHashMap去做清理整个key的操作呢??
原因是在于:在进行put操作后,虽然GC将WeakReference的key中的Integer数组回收了,并将事件通过到了ReferenceQueue,但是后续却没有相应的动作去触发WeakHashMap来进行处理ReferenceQueue,所以WeakReference包装的key依然存在在WeakHashMap中,其对应的value也就依然存在。
但是在WeakHashMap中会删除那些已经被GC的键值对在源码中是通过调用expungeStaleEntries函数来完成的,而这个函数只在WeakHashMap的put、get、size()等方法中才进行了调用。因此,只有put、get、size()方法来可以触发WeakHashMap来进行处理ReferenceQueue。
以上也就是为什么上面的第二段代码中调用下WeakHashMap的size()方法之后就不会报异常能正常工作的原因。
到此这篇关于Java中WeakHashMap和HashMap的区别详解的文章就介绍到这了,更多相关WeakHashMap和HashMap的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!