WeakHashMap源码分析

总结

WeakHashMap 实现了Map接口,但是它与HashMap的区别是 key为WeakReference类型,当key中的对象不再是强引用或者软引用时,垃圾收集器将key自动回收。
WeakHashMap支持null key和null value

WeakHashMap的应用场景

由于垃圾收集器回收一个对象时,通过对象可达性分析算法进行回收。如果我们创建了一个HashMap,HashMap中存放了对象。除非把HashMap对象回收掉,否则HashMap中的对象将不会被回收,根据对象可达性原则,因为HashMap中引用了此对象。在一个大的Hash表中,将会浪费内存。
应用例子:

  • JDK ThreadLocal 实现
    具体的源码不在详细的分析。ThreadLocal是一个map结构,每个Thread对象都有一个ThreadLocalMap对象,用来存放此线程的本地变量,其中map中的key为ThreadLocal,value为需要存放的值。hash冲突使用线性探测的方式。
    当本地创建的ThreadLoal的强引用不可达时,虚拟机在进行垃圾回收时把这些对象自动回收,然后放入Queue中,通过遍历queue移除map中key为null的entry,节约了不必要的内存浪费。

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

源码解读

WeakHashMap继承了抽象类AbstractMap 并实现了Map接口。

  public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
WeakHashMap实例字段及类字段

默认容量 16

   private static final int DEFAULT_INITIAL_CAPACITY = 16;

最大容量 必须是2的幂 默认1<<30

  private static final int MAXIMUM_CAPACITY = 1 << 30;

负载因子 默认值是0.75 (负载因子跟Hash冲突有关,因子越大,冲突就越大)

   private static final float DEFAULT_LOAD_FACTOR = 0.75f;

底层数据结构(数组+链表);数组长度必须是2的幂

    Entry<K,V>[] table;

map中实际元素的数量

private int size;

当下次动态扩容时map中元素数量

 private int threshold;

负载因子

     private final float loadFactor;

虚引用队列。队列中存放被垃圾收集器回收的Entry中的key

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

记录map修改次数的字段(此字段主要用于标识在迭代map时,如果map结构被改变,那么它将出现fail-fast错误,抛出ConcurrentModificationException)

 int modCount;
底层数据结构实体类(是一个链表结构)
    /**
     * The entries in this hash table extend WeakReference, using its main ref
     * field as the key.
     */
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        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;
        }
移除被垃圾回收的对象

由于WeakHashMap的key为弱引用,value为强引用。在创建key的时候注册了ReferenceQueue,因此被垃圾回收key的存放到ReferenceQueue中。通过遍历Queue,把queue对应的key在map中删除。由于queue与map中的key是同一个对象,所以使用内存地址的比较,一定使用==

    /**
     *实现思路:
     *1.从队列里poll()元素,直到队列为空
     *2.hash 到table中对应的元素
     *3.由于数组里的元素是一个链表结构。当需要删除的元素是头元素
     *是,把table[i]设置为头元素的下一个元素。即table[i]=p
     *否则:直接删除。即pre.next =p.next。并把value设置为null
     */
    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;
                    /*特别注意,此时的key为null,但是内存地址
                    * 没变,因此使用==比较。这里涉及到equals
                    * 与==的区别。
                    * == 的用法:1.对于基本数据类型,是值比
                    * 较。对于引用对象类型,使用的是内存地址
                    * equals:引用对象的值比较。
                    */
                  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;
                }
            }
        }
    }
如何存放key为null的值

由于key为null代表着被垃圾收集器进行了回收。因此WeakHashMap中当key为null时,它实际上在集合中存放的是一个Object的空对象。

   /**
     * Value representing null keys inside tables.
     */
    private static final Object NULL_KEY = new Object();
put方法实现

先判断key是否为null,如果为null,则转变为Object.
然后进行依据key得到hash值,定位到table,由于table中的元素是一个链表结构。因此对链表进行遍历。

 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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值