WeakHashMap源码分析

基于jdk1.8源码实现的。

WeakHashMap也是一个“数组和链表”的结合体。

继承结构

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

通过上面可以看到继承了AbstractMap类,实现了Map接口。

与HashMap的区别在于并没有实现Cloneable和Serializable接口,这就导致WeakHashMap对象不能被克隆和序列化。

成员属性

    //默认初始化大小
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    //最大容量
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    //默认装载因子
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //内部数组
    Entry<K,V>[] table;
    //元素
    private int size;
    //扩容阈值
    private int threshold;
    //装载因子
    private final float loadFactor;
    //引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到队列中。
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

这里拓展一下,因为设计到一个可达性的问题,所以插入一个关于可达性的Reference。

  • Reference

  • 主要是负责内存的一个状态,当然它还和java虚拟机,垃圾回收器打交道。Reference类首先把内存分为4种状态Active,Pending,Enqueued,Inactive。

  • Active,一般来说内存一开始被分配的状态都是 Active,
  • Pending 大概是指快要被放进队列的对象,也就是马上要回收的对象,
  • Enqueued 就是对象的内存已经被回收了,我们已经把这个对象放入到一个队列中,方便以后我们查询某个对象是否被回收,
  • Inactive就是最终的状态,不能再变为其它状态。

当执行gc之后,状态从active变成pending,然后保存在队列中,状态则变为Enqueued,当从队列中取出该元素那么变成终态Inactive。

构造函数

    public WeakHashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Initial Capacity: "+
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load factor: "+
                                               loadFactor);
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        table = newTable(capacity);
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
    }

    public WeakHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public WeakHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public WeakHashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                DEFAULT_INITIAL_CAPACITY),
             DEFAULT_LOAD_FACTOR);
        putAll(m);
    }

可以看到下面三个构造方法都是通过调用第一个构造方法实现的。传入参数有两个一个是初始化容量大小,另外一个是装载因子。

成员方法

put(K key, V value)

向WeakHashMap中添加键值对

    //向WeakHashMap中添加键值对
    public V put(K key, V value) {
        /* private static Object maskNull(Object key) {
         *   return (key == null) ? NULL_KEY : key;
         * }
         */
        Object k = maskNull(key);
        //通过key的hash值
        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) {
            //如果存在,进行value更新
            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);
        //更新size,维护阈值,如果需要扩容,那么扩容为原来的两倍
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

    private static final Object NULL_KEY = new Object();

    private Entry<K,V>[] getTable() {
        expungeStaleEntries();
        return table;
    }
    
    //删除过时的条目,即将ReferenceQueue队列中的对象引用全部在table中给删除掉
    //思路:如何删除一个table的节点e,方法为:首先计算e的hash值,
    //接着根据hash值找到其在table的位置,然后遍历链表即可。
    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;
                    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,如果为null,则将key用一个Object常量代替:NULL_KEY。在HashMap中是没有进行这样一个替代转换的,而是直接用null作为key存在在HashMap对象中。这是他们其中的一个区别;

第二步:计算key的hash值;

第三步:根据hash值找到其在table的存储位置 i;

第四步:由于table的每个位置存储的可能是一个链表,因此,在此位置 i处的链表中检测是否有此key存在,如果有,则更新其key所对应的value即可。如果没有此key,则将此节点加入到此链表的头结点位置。

在getTable中涉及到一个方法,expungeStaleEntries方法是删除一些元素。这个在HashMap的实现中并不存在。

resize(int newCapacity)

对于任何容器,当存储数据的空闲位置到达阈值时,都会进行扩容,WeakHashMap也不例外,因此,也有必要分析下此类的resize()方法。

    void resize(int newCapacity) {
        //获取内存中的数据
        Entry<K,V>[] oldTable = getTable();
        //原来数组的长度
        int oldCapacity = oldTable.length;
        //长度数据的合法性校验
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        
        //创建一个新的table,newCapacity是传入的
        Entry<K,V>[] newTable = newTable(newCapacity);
        //元素转移
        transfer(oldTable, newTable);
        //引用重新指向
        table = newTable;

        //更新维护阈值
        if (size >= threshold / 2) {
            threshold = (int)(newCapacity * loadFactor);
        } else {//删除过时的元素,进行元素转移
            expungeStaleEntries();
            transfer(newTable, oldTable);
            table = oldTable;
        }
    }

扩容其实思想特简单,就是分配一个是原来空间的2倍的数组空间,接着进行一个数组的拷贝即可。其中,在进行上面步骤的过程中,涉及到一个特殊情况的处理。

方法的源码如下:(有一点主要注意:在transfer方法中用到了Entry类的get方法,这个方法是WeakReference的父类Reference中的方法,此方法的功能是:获得该引用所指示的对象)。transfer源码如下:

transfer(Entry<K,V>[] src, Entry<K,V>[] dest)

    private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
        for (int j = 0; j < src.length; ++j) {
            Entry<K,V> e = src[j];
            src[j] = null;
            while (e != null) {
                Entry<K,V> next = e.next;
                Object key = e.get();
                if (key == null) {
                    e.next = null;  // Help GC
                    e.value = null; //  "   "
                    size--;
                } else {
                    int i = indexFor(e.hash, dest.length);
                    e.next = dest[i];
                    dest[i] = e;
                }
                e = next;
            }
        }
    }
    
    //Reference中get方法实现
    public T get() {
        return this.referent;
    }

代码实现比较简单,此处不再赘述。

get(Object key)

根据key返回对应的value或者null

    public V get(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int index = indexFor(h, tab.length);
        Entry<K,V> e = tab[index];
        while (e != null) {
            if (e.hash == h && eq(k, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }

实现思路如下:首先对key是否为空进行处理,计算key的hash值,再计算对应的位置。如果此时对应元素为空,直接返回null,否则进入死循环,也就是此时是一个链表的情况。如果找到对应key对应的value值,那么直接返回value,否则没有找到,返回null。

containsKey(Object key)

    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

看到调用的是getEntry方法,继续看源码。

getEntry(Object key)

    Entry<K,V> getEntry(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int index = indexFor(h, tab.length);
        Entry<K,V> e = tab[index];
        while (e != null && !(e.hash == h && eq(k, e.get())))
            e = e.next;
        return e;
    }

实现思路类似于get的实现思路。此处不再一一赘述。

remove(Object key)

根据key删除元素。

    public V remove(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
        Entry<K,V> prev = tab[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            if (h == e.hash && eq(k, e.get())) {
                modCount++;
                size--;
                if (prev == e)
                    tab[i] = next;
                else
                    prev.next = next;
                return e.value;
            }
            prev = e;
            e = next;
        }

        return null;
    }

其他方法比如size、isEmpty、putAll、clear等方法不再一一粘贴。还请批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值