WeakHashMap源码分析

来源:互联网 发布:java 小游戏源代码 编辑:程序博客网 时间:2024/05/30 23:06

总结

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;    }
原创粉丝点击