Java WeakHashMap
作为一个java开发者肯定都知道且使用HashMap,但估计大部分人都不太知道WeakHashMap。从类定义上来看,它和普通的HashMap一样,继承了AbstractMap类和实现了Map接口,也就是说它有着与HashMap差不多的功能。那么既然jdk已经提供了HashMap,为什么还要再提供一个WeakHashMap呢? 黑格尔曾经说过,存在必合理,接下来我们来看下为什么有WeakHashMap。 源码分析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%它就会自动扩容。 Entryprivate 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失去所有强引用的时候感知到。 putpublic 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中没有树化。 getpublic 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中的位置,然后遍历一下单链表就知道了。 expungeStaleEntriesprivate 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里唯一加了同步的地方。 其他除了上述几个和HashMap不太一样的地方外,WeakHashMap也提供了其他HashMap所有的方法,比如像remove、clean、putAll、entrySet…… 功能上几乎可以完全替代HashMap,但WeakHashMap也有一些自己的缺陷。 缺陷1.非线程安全关键修改方法没有提供任何同步,多线程环境下肯定会导致数据不一致的情况,所以使用时需要多注意。 2.单纯作为Map没有HashMap好 HashMap在Jdk8做了好多优化,比如单链表在过长时会转化为红黑树,降低极端情况下的操作复杂度。但WeakHashMap没有相应的优化,有点像jdk8之前的HashMap版本。 3.不能自定义ReferenceQueueWeakHashMap构造方法中没法指定自定的ReferenceQueue,如果用户想用ReferenceQueue做一些额外的清理工作的话就行不通了。如果即想用WeakHashMap的功能,也想用ReferenceQueue,貌似得自己实现一套新的WeakHashMap了。 用途这里列举几个我所知道的WeakHashMap的使用场景。 1.阿里Arthas在阿里开源的Java诊断工具中使用了WeakHashMap做类-字节码的缓存。 // 类-字节码缓存 private final static Map<Class<?>/*Class*/,byte[]/*bytes of Class*/> classBytesCache = new WeakHashMap<Class<?>,byte[]>(); 2.CacheWeakHashMap这种自清理的机制,非常适合做缓存了。 3.ThreadLocalMap ThreadLocal中用ThreadLocalMap存储Thread对象,虽然ThreadLocalMap和WeakHashMap不是一个东西,但ThreadLocalMap也利用到了WeakReference的特性,功能用途很类似,所以我很好奇为什么ThreadLocalMap不直接用WeakHashMap呢! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |