Java8中的WeakHashMap

什么是WeakHashMap?

WeakHashMap是基于java弱引用实现的HashMap,感觉一句话就讲的差不多了嘿嘿。
先看看它的定义:

public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>

如上,在定义方面和HashMap并没有什么不同。而且在结构上,基本和HashMap一致。在Java8中,唯一存储上的不同点就是,当冲突的key变多时,HashMap引入了二叉树(红黑树)进行存储,而WeakHashMap则一直使用链表进行存储。


而WeakHashMap的特点,这里也有总结:

  • 基于map接口,是一种弱键相连,WeakHashMap里面的键会自动回收
  • 支持 null值和null键。和HashMap有些相似
  • fast-fail机制
  • 不允许重复
  • WeakHashMap经常用作缓存关于HashMap的具体存储结构,这里就不细讲了,大家可以看我这篇深入分析的博客:集合源码学习:HashMap

Java里面引用分为4中类型,大点分有强,弱,大家可以参阅我这篇文章结合源码详细介绍4种引用。Java里面引用分类 ,而在WeakHashMap则主要用到了WeakReference这个引用,这篇文章就主要分析,为什么WeakHashMap具有如此特性(主要是Weak)。

关于Entry<K,V>

和HashMap一样,WeakHashMap也是用一个Entry实体来构造里面所有的元素的,但是这个Entry却和HashMap的不同,他是弱引用。

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

如上,Entry还继承了WeakReference,所以Entry是个弱引用。何为弱引用呢?就是就是每当进行一次gc,你这个对象就会被清除,当然如果这个对象还存在着软引用或者强引用,就可能不会被清除。

ReferenceQueue queue作用

queue是用来存放那些,被jvm清除的entry的引用,因为WeakHashMap使用的是弱引用,所以一旦gc,就会有key键被清除,所以会把entry加入到queue中。在WeakHashMap中加入queue的目的,就是为expungeStaleEntries所用。

        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时,都将它与queue绑定,从而一旦被jvm回收,那么这个Entry就会倍加如到queue中。

expungeStaleEntries方法具体意思

这里面我的理解和很大一部分的博客都不同,我认为在这个方法里面就仅仅是释放value值。由前面的Entry的构造方法可知, super(key, queue); 传入父类的仅仅是key,所以经过仔细阅读jdk源码开始部分分析后,得出结论,在WeakHashMap中,有jvm回收的,仅仅是Entry的key部分,所以一旦jvm强制回收,那么这些key都会为null,再通过私有的expungeStaleEntries 方法,把value也制null,并且把size--
首先看代码:

    /**
     * 从ReferenceQueue中取出过期的entry,从WeakHashMap找到对应的entry,逐一删除
     * 注意,只会把value置为null。
     */
    private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            //遍历queue
            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) {
                    //遍历table[i]所在链表
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        //queue里面有e,那就删了。
                        if (prev == e)
                            //e就是当前的p.next
                            table[i] = next;
                        else
                            prev.next = next;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        //置为null,帮助gc。只制null了value。
                        e.value = null; // Help GC
                        //设置e的value,但是没看到设置e的key。
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

上面代码逻辑为,当在table中找到queue中存在元素时,就把value制空,然后size--。所以在WeakHashMap中,就只有key被回收。下面看一个实例验证。

首先需要了解一点:expungeStaleEntries方法在哪些方面会被调用?
经过阅读源码,发现expungeStaleEntries方法只在一下几个地方被调用:

  • private Entry<K,V>[] getTable() 里面,而这个getTable则在下列方法被调用:
    • public V get(Object key)
    • Entry<K,V> getEntry(Object key)
    • public V put(K key, V value)
    • void resize(int newCapacity)
    • public V remove(Object key)
    • boolean removeMapping(Object o)
    • public boolean containsValue(Object value)
    • private boolean containsNullValue()
    • public void forEach(BiConsumer<? super K, ? super V> action)
    • public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
  • public int size()
  • void resize(int newCapacity)

大致了解了上面调用情况后,现在看一段测试代码。

测试:

        public static void main(String[] args) throws InterruptedException {
        WeakHashMap<String, String> map = new WeakHashMap<String, String>();
        for(int i = 0;i < 5;i++){
            map.put(new String("字符串"+i), new String("串串"+i));
        }
        System.gc();
        TimeUnit.SECONDS.sleep(2);
        System.out.println(map.size());
        map.put(new String("字符串"+6), new String("串串"+6));
    }

当我在WeakHashMap源码的size方法里面打一个断点时候:
这里写图片描述

之后,程序直接掉进这里面,从这里面可以看出size大小以及table详情看下图:
size大小

table详情

所以,从上面可以看出,WeakHashMap中,key会被gc,而value,则是通过expungeStaleEntries赋值为null。

疑问

验证我的思维过程中,遇到了一个很神的问题:

我在测试过程中,真的是测了好多次代码,才发现这样可以得到我所推理出的情况,但是,当我把断点打到上面测试代码第8行时,size为0,并且table也完全为空。

后来我发现,不管在哪里打,只要你在你测试类打,然后再跳到源码去看,size还是为0,甚至我在expungeStaleEntries里面加一个断点,却无法被调用。

本来尝试把整个jdk项目放到eclipse里满去里面查找,但是内存耗不起,加载半天加载不出来。

如果哪位老哥遇到过或者解决过这个问题,请在下方留言帮助我解答这个疑惑吧,感激不尽。花了好些天学习WeakHashMap。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/98432.html

(0)
上一篇 2021年8月21日
下一篇 2021年8月21日

相关推荐

发表回复

登录后才能评论