Java WeakHashMap

  做为一个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

Entry

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

put

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中的死对象作清理,稍后会详细介绍。缓存

get

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中的位置,而后遍历一下单链表就知道了。
安全

expungeStaleEntries

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也有一些本身的缺陷。
工具

缺陷

1.非线程安全

  关键修改方法没有提供任何同步,多线程环境下确定会致使数据不一致的状况,因此使用时须要多注意。源码分析

2.单纯做为Map没有HashMap好

  HashMap在Jdk8作了好多优化,好比单链表在过长时会转化为红黑树,下降极端状况下的操做复杂度。但WeakHashMap没有相应的优化,有点像jdk8以前的HashMap版本。
  性能

3.不能自定义ReferenceQueue

  WeakHashMap构造方法中无法指定自定的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.Cache

  WeakHashMap这种自清理的机制,很是适合作缓存了。

3.ThreadLocalMap

  ThreadLocal中用ThreadLocalMap存储Thread对象,虽然ThreadLocalMap和WeakHashMap不是一个东西,但ThreadLocalMap也利用到了WeakReference的特性,功能用途很相似,因此我很好奇为何ThreadLocalMap不直接用WeakHashMap呢!
版权声明:本文为博主原创文章,转载请注明出处。 博客地址:xindoo.blog.csdn.net/

相关文章
相关标签/搜索