Java中的WeakHashMap实现分析

转:https://www.dexcoder.com/selfly/article/289

在Java集合中有一种特殊的Map类型:WeakHashMap。 WeakHashMap 继承于AbstractMap,实现了Map接口。 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。 不过WeakHashMap的键是“弱键”,里面存放了键对象的弱引用,当某个键不再正常使用时,会从WeakHashMap中被自动移除。当一个键对象被垃圾回收,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。

那么这个“弱键”的原理呢?大致上是通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是: (01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。 (02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。 (03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。 这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。 和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。

WeakHashMap源码分析,WeakHashMap维护了一个ReferenceQueue,保存了所有存在引用的Key对象。WeakHashMap.Entry<K,V>中并没有保存Key,只是将Key与ReferenceQueue关联上了。

注:当时发现,明明Entry 使用 WeakReference super方法  
WeakReference(T referent, ReferenceQueue<? super T> q) 初始化时,
queue中加入的是key对象,为何poll的时候对象x的类型为Entry<X,Y>,后查看源码发现:
若初始化时使用到了ReferenceQueue,当对应的key(referent)对象除了WeakReference外没有其他直达的引用时,
遇到GC回收时会调用基类Reference
中的enqueue方法:

/**
     * Adds this reference object to the queue with which it is registered,
     * if any.
     *
     * <p> This method is invoked only by Java code; when the garbage collector
     * enqueues references it does so directly, without invoking this method.
     *
     * @return   <code>true</code> if this reference object was successfully
     *           enqueued; <code>false</code> if it was already enqueued or if
     *           it was not registered with a queue when it was created
     */
    public boolean enqueue() {
        return this.queue.enqueue(this);
    }


    /* -- Constructors -- */

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }


可以看出加入到 queue中的是 this,即继承WeakReference的 Entry对象。那么自然而然,之后的poll拿到的也是Entry而不是Key了。
结论:若一个对象 A 继承了WeakReference,且初始化时使用到了ReferenceQueue

public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

那么key,referent(通过使用对象主键) 用来决定当GC 来到时,是否还有其他引用。当确定无其他引用时,加入到queue的对象是 A。

同时初始化方法

1、WeakReference(T referent):

未用到ReferenceQueue,GC 遇到时 不存在其他引用时直接回收。

获取对象:

public T get() {
        return this.referent;
    }


2、public WeakReference(T referent, ReferenceQueue<? super T> q)

GC 遇到时 不存在其他引用时,将上文分析后的

继承对象(有继承)或 T(无继承)放入queue,对象类型有所不同。

referenceQueue的 poll 方法 最终返回的都是 Reference 的子类:

继承的返回  继承对象 this;无继承的返回 weakreference


  
  
  1. private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
  
  
  1. // Entry是单向链表。
  2. // 它是 “WeakHashMap链式存储法”对应的链表。
  3. // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value),
  4. // equals(Object o), hashCode()这些函数
  5. private static class Entry<K, V> extends WeakReference<K> implements
  6. Map.Entry<K, V> {
  7. private V value;
  8. private final int hash;
  9. // 指向下一个节点
  10. private Entry<K, V> next;
  11. // 构造函数。
  12. Entry(K key, V value, ReferenceQueue<K> queue, int hash,
  13. Entry<K, V> next) {
  14. super(key, queue);
  15. this.value = value;
  16. this.hash = hash;
  17. this.next = next;
  18. }
  19. ...
  20. }

WeakHashMap中有一个私有的expungeStaleEntries()方法,会在大部分共有方法中被调用。这个方法会将ReferenceQueue中所有失效的引用从Map中去除。

  
  
  1. // 清空table中无用键值对。原理如下:
  2. // (01) 当WeakHashMap中某个“弱引用的key”由于没有再被引用而被GC收回时,
  3. // 被回收的“该弱引用key”也被会被添加到"ReferenceQueue(queue)"中。
  4. // (02) 当我们执行expungeStaleEntries时,
  5. // 就遍历"ReferenceQueue(queue)"中的所有key
  6. // 然后就在“WeakReference的table”中删除与“ReferenceQueue(queue)中key”对应的键值对
  7. private void expungeStaleEntries() {
  8. Entry<K, V> e;
  9. while ((e = (Entry<K, V>) queue.poll()) != null) {
  10. int h = e.hash;
  11. int i = indexFor(h, table.length);
  12. Entry<K, V> prev = table[i];
  13. Entry<K, V> p = prev;
  14. while (p != null) {
  15. Entry<K, V> next = p.next;
  16. if (p == e) {
  17. if (prev == e)
  18. table[i] = next;
  19. else
  20. prev.next = next;
  21. e.next = null; // Help GC
  22. e.value = null; // " "
  23. size--;
  24. break;
  25. }
  26. prev = p;
  27. p = next;
  28. }
  29. }
  30. }

需要注意,WeakHashMap的Key是弱引用,Value不是。WeakHashMap不会自动释放失效的弱引用,仅当包含了expungeStaleEntries()的共有方法被调用的时候才会释放。

一个简单的例子:

  
  
  1. public static void main(String args[]) {
  2. WeakHashMap<String, String> map = new WeakHashMap<String, String>();
  3. map.put(new String("1"), "1");
  4. map.put("2", "2");
  5. String s = new String("3");
  6. map.put(s, "3");
  7. while (map.size() > 0) {
  8. try {
  9. Thread.sleep(500);
  10. } catch (InterruptedException ignored) {
  11. }
  12. System.out.println("Map Size:" + map.size());
  13. System.out.println(map.get("1"));
  14. System.out.println(map.get("2"));
  15. System.out.println(map.get("3"));
  16. System.gc();
  17. }
  18. }

运行结果(一直循环当中):

Map Size:3 1

2

3

Map Size:2

null

2

3

 

根据String的特性,

元素“1”的key已经没有地方引用了,所以进行了回收。

元素“2”是被放在常量池中的,所以没有被回收。

元素“3”因为还有变量s的引用,所以也没有进行回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值