Java集合·09·WeakHashMap详解

一、概述

WeakHashMap 继承于AbstractMap,实现了Map接口。

WeakHashMap也是一个散列表,它存储的内容是键值对(key-value)映射,而且键和值都可以为null。与其他散列表不同的是,WeakHashMap的键是弱键。即在WeakHashMap中当某个键不再正常使用时,会被从WeakHashMap中自动移除。更精确的说,对于给定的一个键,其映射的存在并不影响垃圾回收器对该键的回收,这就使得该键是可终止的。被终止,然后被回收。当一个键被终止时,它对应的键值对也就从映射中移除了。

二、数据结构

拉链法,和HashMap类似(链表模式),只是key保存方式不一样,是WeakReference,使用时需要先判断key是否存在。

另外包含一个queue保存已被GC清除”的“弱引用的键”。

private final ReferenceQueue queue = new ReferenceQueue<>();
 
 

ReferenceQueue

Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

使用方式:

ReferenceQueue queue = new ReferenceQueue();
WeakReference ObjectRefer = new WeakReference(object, queue);

当object被回收后,queue中就会添加这个ObjectRefer。

Entry

继承 WeakReference,实现了Map.Entry接口,使用WeakReference保存key,另外保存hash值,value和后续节点引用

        Entry(Object key, V value,
              ReferenceQueue queue,
              int hash, Entry next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
 
 

三、特点

  • 支持键、值为null

  • 键为“弱键”(值还是强引用)

    弱键是指什么?是指在WeakHashMap中的key的引用并不影响key被垃圾回收器回收。当key被回收时,WeakHashMap中的键值对也将被移除。

  • 非线程安全。可使用Collections.synchronizedMap

  • Iterator是fast-fail的

四、要点实现

1.弱键实现原理

通过WeakReference和ReferenceQueue实现。

WeakHashMap的key是“弱键”,是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。

  1. 新建WeakHashMap,将“键值对”添加到WeakHashMap中。 实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
  2. 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
  3. 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。

使用ReferenceQueue

在初始化entry时将key使用WeakReference保存,并设置回调队列为类中ReferenceQueue

        Entry(Object key, V value,
              ReferenceQueue queue,
              int hash, Entry next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
 
 

清除已被终止的key的键值队映射

    private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry e = (Entry) x;
                int i = indexFor(e.hash, table.length);

                Entry prev = table[i];
                Entry p = prev;
                while (p != null) {
                    Entry 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;
                }
            }
        }
    }

2.弱键对基本方法实现的影响

resize时,由于弱键的存在可能会发生空间浪费。

在resize之后,判断当前size是否大于threshold/2,当小于时放弃这次扩容操作,依然使用之前的数组,并进行一次清除操作。

所有key相关方法

使用key.get()获取真实key进行操作,同时考虑key已经被回收导致的key==null情况。

clear操作

在原有操作基础上先清除queue。

    public void clear() {
        // clear out ref queue. We don't need to expunge entries
        // since table is getting cleared.
        while (queue.poll() != null)
            ;

        modCount++;
        Arrays.fill(table, null);
        size = 0;

        // Allocation of array may have caused GC, which may have caused
        // additional entries to go stale.  Removing these entries from the
        // reference queue will make them eligible for reclamation.
        while (queue.poll() != null)
            ;
    }

支持key为null

使用一个NULL_OBJECT表示为null值。避免key被回收后获取到null与原有null键冲突。

3.被回收数据清除时机

对数据进行操作和查询之前会先进行清除操作。

4.HashIterator

实现了Iterator接口

其中使用了两个Object保存key值

nextKey, 添加强引用,避免在调用hasNext()和next()方法之间key被回收掉。在hasNext()方法中更新nextKey。

currentKey,添加强引用,保证next()需要使用这个key时不为null,在next()方法中更新。

        private int index;
        private Entry entry = null;
        private Entry lastReturned = null;
        private int expectedModCount = modCount;

        /**
         * Strong reference needed to avoid disappearance of key
         * between hasNext and next
         */
        private Object nextKey = null;

        /**
         * Strong reference needed to avoid disappearance of key
         * between nextEntry() and any use of the entry
         */
        private Object currentKey = null;

so,使用Iterator遍历Map时不会出现key被回收,返回错误数据的情况。保证遍历到的数据都是可用状态。

你可能感兴趣的:(Java集合·09·WeakHashMap详解)