平时我们使用最多的数据结构肯定是 HashMap,但是在使用的时候我们必须知道每个键值对的生命周期,并且手动清除它;但是如果我们不是很清楚它的生命周期,这时候就比较麻烦;通常有这样几种处理方式:
创新互联是一家专注于网站建设、成都网站建设与策划设计,延边朝鲜族网站建设哪家好?创新互联做网站,专注于网站建设10年,网设计领域的专业建站公司;建站业务涵盖:延边朝鲜族等地区。延边朝鲜族做网站价格咨询:028-86922220
由一个线程定时处理,可以是Timer
或者ScheduledThreadPoolExecutor
;
利用重写LinkedHashMap.removeEldestEntry()
,实现 FIFOCache 或者 LRUCache;可以参考我之前写的一篇文章 LinkedHashMap 相关;
利用 WeakHashMap
的特性,如果逻辑比较复杂还可以直接使用Reference
;这里可以参考 Reference 完全解读 和 Reference 框架概览;
所以本文将主要介绍WeakHashMap
的特性,以及补充一些关于 HashMap 实现的对比;相关 HashMap 的介绍也可以参考 HashMap 相关;
上面也介绍了,WeakHashMap
适用于不是非常重要的缓存类似的场景;例如:
WeakHashMap
// 打印:
100
100
100
46
{}
0
对于以上的结果你可能和我打印的不一样,WeakHashMap
按照语义应该是,当 key 没有强引用指向的时候,会自动清除 key 和 value;我这里先解释它的释放过程,如果你觉得很清晰,那WeakHashMap
你就算是掌握了;
首先 for 循环结束的时候,key 已经没用强引用指向了,此时所有的 key 都是弱引用了;
接下来执行1,因为我这里只有一个方法,新生代还有足够的空间,所以不会触发 GC,所以所有的 key 任然在堆里面,所以打印100;
然后手动触发 GC,虽然System.gc();
不一定会立即执行,但是我这里只有一个方法,所以肯定会执行 GC,这里可以打开 GC 日志查看,-verbose:gc
;因为 所有的 key 都是弱引用,所以referent
被致为 null,同时将 key 注册到 ReferenceQueue
中;
在执行 3-7 的时候,按语义 map 应该为空;但是将 key 注册到 ReferenceQueue
并非原子性一次完成的,所以这里会打印不同的值,每注册完成一个,在 map 进行操作的时候,就会将其移除;
将上面的代码改成多线程分析思路也是一样的,如果你觉得有不清楚的地方可以查看下文;
public class WeakHashMapextends AbstractMap implements Map
可以看到虽然WeakHashMap
也是基于哈希表,但是却并非像LinkedHashMap
一样是继承于HashMap
,并且WeakHashMap
也没有实现Cloneable, Serializable
两个接口,这是因为WeakHashMap
基于WeakReference
实现的,弱引用并不建议实现序列化,同时弱引用一般用于不是很重要的缓存,也就没必要实现Cloneable, Serializable
两个接口了;
private final ReferenceQueue
上面代码所列的ReferenceQueue,Entry,expungeStaleEntries()
就是WeakHashMap
实现的核心了;这里强烈建议要先看 Reference 完全解读 和 Reference 框架概览这两篇文章,里面同样的内容我也不会再赘述了;
Entry
, 表明所有的节点都是WeakReference
,而 key 则是 referent;
queue,所有 key 使用同一个ReferenceQueue
监听器,每当 key 被回收的时候,entry 将会被注册到ReferenceQueue
中;
expungeStaleEntries,将注册到ReferenceQueue
中的 entry 移除,并将 value 置为 null;WeakHashMap
的所有操作都先执行expungeStaleEntries
,这样WeakHashMap
就实现了自动回收不在需要的 key 和 value;
其实上面的内容就已经将WeakHashMap
的主要实现讲完了,但是我之前在看HashMap
源码的时候,并没有对比 JDK1.7 和 JDK1.8,但是在这里发现其实WeakHashMap
的实现和 JDK1.7 差不多,所以接下来我将简单对比一下WeakHashMap
和HashMap
;
在WeakHashMap
和HashMap
中都要求容量是2的幂,因为当容量为2的幂时,使用除留余数法计算哈希桶位置时可以使用hash % length = hash & (length-1)
的性质进行优化;
// WeakHashMapint capacity = 1;while (capacity < initialCapacity) capacity <<= 1;// HashMapstatic final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
简单测试可以得到:
initCap = 10 | 50 | 100 | |
---|---|---|---|
WeakHashMap | 30 | 32 | 26 |
HashMap | 3 | 3 | 3 |
代码比较简单我就不贴了,从上表也可以看到了tableSizeFor
不仅高效而且稳定;
// WeakHashMapfinal int hash(Object k) { int h = k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }// HashMapstatic final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
两种hash算法都是要避免极端的hashCode()
,但是HashMap
却更为透彻,因为影响哈希桶位置的只有 hash 的低位(容量2的n次方,n个低位),直接将高位与上低位,使高位 hash 参与位置计算,简洁且高效;
此外还有put
方法,但是里面还牵涉红黑树,对于本文就扯得有点远了,所以暂不讲;
WeakHashMap
是WeakReference
的典型应用,在灵活应用WeakHashMap
之后,如果有更为复杂的逻辑,可以直接使用Reference
实现;
另外WeakHashMap
的自动回收机制是操作时检查,所以WeakHashMap
里面即使有可回收对象,但是很久都没有操作也是没法及时清理,所以在使用的时候,需要经常对它操作一下,才能及时回收垃圾。