zoukankan      html  css  js  c++  java
  • 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。接下来让我们看下它的代码,看它具体是怎么实现的。
      

    源码分析

        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%它就会自动扩容。

    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失去所有强引用的时候感知到。
      

    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呢!
    版权声明:本文为博主原创文章,转载请注明出处。 博客地址:https://xindoo.blog.csdn.net/

  • 相关阅读:
    使用CORS解决flask前端页面跨域问题
    re.search 与 re.match的区别
    jupyter-notebook快捷键的使用
    docker端口的映射顺序
    [转]/dev/null 命令用法
    [转]Docker容器内不能联网的6种解决方案
    牛客网python试题-错误整理-20180711
    docker常用命令
    linux实用命令-待补充
    docker exec小脚本
  • 原文地址:https://www.cnblogs.com/xindoo/p/11421734.html
Copyright © 2011-2022 走看看