做为一个java开发者确定都知道且使用HashMap,但估计大部分人都不太知道WeakHashMap。从类定义上来看,它和普通的HashMap同样,继承了AbstractMap类和实现了Map接口,也就是说它有着与HashMap差很少的功能。那么既然jdk已经提供了HashMap,为何还要再提供一个WeakHashMap呢? 黑格尔曾经说过,存在必合理,接下来咱们来看下为何有WeakHashMap。
先来想象一下你由于某种需求须要一个Cache,你确定会面临一个问题,就是全部数据不可能都放到Cache里,或者放到Cache里性价比过低了。这个时候你可能很快就想到了各类Cache数据过时策略,目前也有一些优秀的包提供了功能丰富的Cache,好比Google的Guava Cache,它支持数据按期过时、LRU、LFU等策略,但它任然有可能会致使有用的数据被淘汰,没用的数据迟迟不淘汰(若是策略使用得当的状况下这都是小几率事件)。
若是我如今说有种机制,可让你Cache里不用的key数据自动清理掉,用的还留着,没有误杀也没有漏杀你信不信!没错WeakHashMap就是能实现这种功能的东西,这也是它和普通的HashMap不一样的地方——它有自清理的机制。 若是让你实现一种自清理的HashMap,你怎么作? 个人作法确定是想办法先知道某个Key确定没有在用了,而后清理到HashMap中对应的K-V。在JVM里一个对象没用了是指没有任何其余有用对象直接或者间接执行它,具体点就是在GC过程当中它是GCRoots不可达的。 Jvm提供了一种机制能让咱们感知到一个对象是否已经变成了垃圾对象,这就是WeakReference,不了解WeakReference的能够看下我上一篇介绍博客Java弱引用(WeakReferences)。
某个WeakReference对象所指向的对象若是被断定为垃圾对象,Jvm会将该WeakReference对象放到一个ReferenceQueue里,咱们只要看下这个Queue里的内容就知道某个对象还有没有用了。 WeakHashMap就是这么作的,因此这里的Weak是指WeakReference。接下来让咱们看下它的代码,看它具体是怎么实现的。
java
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;复制代码
和普通HashMap同样,WeakHashMap也有一些默认值,好比默认容量是16,最大容量2^30,使用超过75%它就会自动扩容。
git
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
/*
* 其余代码
*/
}复制代码
它的Entry和普通HashMap的Entry最大的不一样是它继承了WeakReference,而后把Key作成了弱引用(注意只有Key没有Value),而后传入了一个ReferenceQueue,这就让它能在某个key失去全部强引用的时候感知到。
github
public V put(K key, V value) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}复制代码
put方法也很简单,用key的hashcode在tab中定位,而后判断是不是已经存在的key,已经存在就替换旧值,不然就新建Entry,遇到多个不一样的key有一样的hashCode就采用开链的方式解决hash冲突。注意这里和HashMap不太同样的地方,HashMap会在链表太长的时候对链表作树化,把单链表转换为红黑树,防止极端状况下hashcode冲突致使的性能问题,但在WeakHashMap中没有树化。
一样,在size大于阈值的时候,WeakHashMap也对作resize的操做,也就是把tab扩大一倍。WeakHashMap中的resize比HashMap中的resize要简单好懂些,但没HashMap中的resize优雅。WeakHashMap中resize有另一个额外的操做,就是expungeStaleEntries(),就是对tab中的死对象作清理,稍后会详细介绍。缓存
public V get(Object key) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int index = indexFor(h, tab.length);
Entry<K,V> e = tab[index];
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}复制代码
get方法就没什么特别的了,由于Entry里存了hash值和key的值,因此只要用indexFor定位到tab中的位置,而后遍历一下单链表就知道了。
安全
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;
}
}
}
}复制代码
expungeStaleEntries就是WeakHashMap的核心了,它承担着Map中死对象的清理工做。原理就是依赖WeakReference和ReferenceQueue的特性。在每一个WeakHashMap都有个ReferenceQueue queue,在Entry初始化的时候也会将queue传给WeakReference,这样当某个能够key失去全部强应用以后,其key对应的WeakReference对象会被放到queue里,有了queue就知道须要清理哪些Entry里。这里也是整个WeakHashMap里惟一加了同步的地方。 除了上文说的到resize中调用了expungeStaleEntries(),size()中也调用了这个清理方法。另外 getTable()也调了,这就意味着几乎全部其余方法都间接调用了清理。
多线程
除了上述几个和HashMap不太同样的地方外,WeakHashMap也提供了其余HashMap全部的方法,好比像remove、clean、putAll、entrySet…… 功能上几乎能够彻底替代HashMap,但WeakHashMap也有一些本身的缺陷。
工具
关键修改方法没有提供任何同步,多线程环境下确定会致使数据不一致的状况,因此使用时须要多注意。源码分析
HashMap在Jdk8作了好多优化,好比单链表在过长时会转化为红黑树,下降极端状况下的操做复杂度。但WeakHashMap没有相应的优化,有点像jdk8以前的HashMap版本。
性能
WeakHashMap构造方法中无法指定自定的ReferenceQueue,若是用户想用ReferenceQueue作一些额外的清理工做的话就行不通了。若是即想用WeakHashMap的功能,也想用ReferenceQueue,貌似得本身实现一套新的WeakHashMap了。
优化
这里列举几个我所知道的WeakHashMap的使用场景。
在阿里开源的Java诊断工具中使用了WeakHashMap作类-字节码的缓存。
// 类-字节码缓存
private final static Map<Class<?>/*Class*/, byte[]/*bytes of Class*/> classBytesCache
= new WeakHashMap<Class<?>, byte[]>();复制代码
WeakHashMap这种自清理的机制,很是适合作缓存了。
ThreadLocal中用ThreadLocalMap存储Thread对象,虽然ThreadLocalMap和WeakHashMap不是一个东西,但ThreadLocalMap也利用到了WeakReference的特性,功能用途很相似,因此我很好奇为何ThreadLocalMap不直接用WeakHashMap呢!
版权声明:本文为博主原创文章,转载请注明出处。 博客地址:xindoo.blog.csdn.net/