JDK11-WeakHashMap集合

介绍

 WeakHashMap 继承于AbstractMap,实现了Map接口。
    和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null
   不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
    这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。

成员变量

 /**
     * 默认初始容量是16,必须是2的幂
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认加载因子
     */
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 存储数据的Entry数组,长度是2的幂。
     */
    Entry<K,V>[] table;

    /**
     * WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
     */
    private int size;

    /**
     * WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子).
     */
    private int threshold;

    /**
     *加载因子实际大小
     */
    private final float loadFactor;

    /**
     * queue保存的是“已被GC清除”的“弱引用的键”。
     */
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

    /**
     * WeakHashMap被改变的次数
     */
    int modCount;

构造函数

//指定容量大小和加载因子的构造函数  
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);
    }

    /**
     包含子map的构造函数
     */
    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);
    }

核心函数

//根据key获取value
public V get(Object key) {
       //检查key为null则返回NULL_KEY对象
        Object k = maskNull(key);
       //取key的hashCode
        int h = hash(k);
       //通过getTable获取数组,去除所有需要被回收的Entry
        Entry<K,V>[] tab = getTable();
       //计算出该key的下标索引
        int index = indexFor(h, tab.length);
       //获取该索引位置的链表头
        Entry<K,V> e = tab[index];
       遍历链表,根据hashCode值与equals比较双重判断获取value
        while (e != null) {
            if (e.hash == h && eq(k, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }
//判断WeakHashMap中是否包含key
public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
//根据key获取Entry实体
 Entry<K,V> getEntry(Object key) {
//检查key为null则返回NULL_KEY对象
        Object k = maskNull(key);
//获取key的hashCode
        int h = hash(k);
        //通过getTable获取数组,去除所有需要被回收的Entry
        Entry<K,V>[] tab = getTable();
//计算出该key的下标索引
        int index = indexFor(h, tab.length);
//获取该索引位置的链表头
        Entry<K,V> e = tab[index];
 //遍历链表,根据hashCode值与equals比较双重判断获取Entry
        while (e != null && !(e.hash == h && eq(k, e.get())))
            e = e.next;
        return e;
    }

//将k-v添加到映射关系中
public V put(K key, V value) {
        //判断key是否为null,如果为null,new Object个对象
        Object k = maskNull(key);
        //获取hash值
        int h = hash(k);
        //通过getTable获取数据,去除所有需要被回收的Entry
        Entry<K,V>[] tab = getTable();
        //获取到key的下标索引
        int i = indexFor(h, tab.length);
        //遍历链表,根据hashCode值与equals比较双重判断key是否存在,存在则将value替换,并直接返回旧值
        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;
            }
        }
        //若key不存在,则将该节点插入链表头,这里和HashMap不太一样,HashMap是尾部追加
        modCount++;
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        //添加元素后的WeakHashMap大小如果大于等于扩容阈值,则扩容1倍
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

//删除所有被GC回收的Entry
private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {//存在对象被GC,那么就需要移除map中对应的数据
            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) {//如果p节点存在
                    Entry<K,V> next = p.next;//定义next节点指向p的下个节点
                    if (p == e) {//如果p就是当前节点
                        if (prev == e)
                            table[i] = next;//桶中第一个数据就是移除的,直接把第二个节点放到节点的位置
                        else
                            prev.next = next;//把上一个节点的下个节点指向p后面的节点
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        e.value = null; // Help GC
                        size--;//减少WeakHashMap的大小
                        break;//结束
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
//核心扩容方法
void resize(int newCapacity) {
        //获取原Entry数组,获取之前先删除所有需要移除的Entry
        Entry<K,V>[] oldTable = getTable();
        //获取原Entry数组的容量
        int oldCapacity = oldTable.length;
        //如果原数组的容量已经达到最大值2^30,则停止扩容并防止再次扩容
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //实例化新Entry数组
        Entry<K,V>[] newTable = newTable(newCapacity);
        //将原数组中的元素全部放入新数组
        transfer(oldTable, newTable);
        //将底层数组替换为新数组
        table = newTable;

        /*
         * If ignoring null elements and processing ref queue caused massive
         * shrinkage, then restore old table.  This should be rare, but avoids
         * unbounded expansion of garbage-filled tables.
         */
        //如果忽略空元素并处理ref队列导致大量收缩,则恢复旧表。这应该是很少见的,但是避免了垃圾填充表的无限制扩展
        if (size >= threshold / 2) {
            threshold = (int)(newCapacity * loadFactor);
        } else {
            expungeStaleEntries();
            transfer(newTable, oldTable);
            table = oldTable;
        }
    }
//将src数组中的元素全部放入dest数组
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
         //遍历src数组
        for (int j = 0; j < src.length; ++j) {
            //依次将src数组中的元素取出
            Entry<K,V> e = src[j];
            //取出后将src中去除引用
            src[j] = null;
            //遍历链表
            while (e != null) {
                //取当前节点的下一节点next
                Entry<K,V> next = e.next;
                //获取弱键
                Object key = e.get();
                 //弱键为空则说明已被GC回收,将该节点清空,方便GC回收该Entry
                if (key == null) {
                    e.next = null;  // Help GC
                    e.value = null; //  "   "
                    size--;
                } else {
                     //不为空说明没有被回收,重新计算在dest数组中的索引
                    int i = indexFor(e.hash, dest.length);
                     //将该节点插入dest索引i处链表头
                    e.next = dest[i];
                    dest[i] = e;
                }
                e = next;
            }
        }
    }
//将一个map中的所有元素全部放入WeakHashMap
public void putAll(Map<? extends K, ? extends V> m) {
         //取传入map的大小
        int numKeysToBeAdded = m.size();
          //若传入的是一个空map,直接return
        if (numKeysToBeAdded == 0)
            return;

        /*
         * Expand the map if the map if the number of mappings to be added
         * is greater than or equal to threshold.  This is conservative; the
         * obvious condition is (m.size() + size) >= threshold, but this
         * condition could result in a map with twice the appropriate capacity,
         * if the keys to be added overlap with the keys already in this map.
         * By using the conservative calculation, we subject ourself
         * to at most one extra resize.
         */
        //如果要添加映射的数量大于或等于阈值,这是保守的;明显的条件是(m.size() + size) >= 阈值,
        //但是这个条件可能导致map的容量是适当容量的两倍,如果要添加的键与此映射中已经存在的键重叠。
        //通过使用保守的计算,我们最多可额外调整一次大小。
        if (numKeysToBeAdded > threshold) {
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
            if (targetCapacity > MAXIMUM_CAPACITY)
                targetCapacity = MAXIMUM_CAPACITY;
            int newCapacity = table.length;
            while (newCapacity < targetCapacity)
                newCapacity <<= 1;
            if (newCapacity > table.length)
                resize(newCapacity);
        }
         //遍历map,依次将元素存入WeakHashMap中
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

//根据key将Entry移除WeakHashMap
public V remove(Object key) {
        //检查key为null则返回NULL_KEY对象
        Object k = maskNull(key);
        //取key的hashCode
        int h = hash(k);
        //获取Entry数组,获取之前先删除所有需要被移除的Entry
        Entry<K,V>[] tab = getTable();
        //计算出该key的下标索引
        int i = indexFor(h, tab.length);
        //取索引i处的Entry,用来遍历链表时存放上个节点
        Entry<K,V> prev = tab[i];
        //用来遍历链表时存放当前节点
        Entry<K,V> e = prev;
        //遍历链表
        while (e != null) {
             //取当前节点的下一节点next
            Entry<K,V> next = e.next;
             //根据hashCode值与equals比较双重判断key是否相同,存在则移除该节点并返回该节点value
            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;
    }

//移除WeakHashMap中指定Entry
boolean removeMapping(Object o) {
         //判断对象o是否为Entry实例,不是则直接return
        if (!(o instanceof Map.Entry))
            return false;
        //获取Entry数组,获取之前先删除所有需要被移除的Entry
        Entry<K,V>[] tab = getTable();
        //将对象o强转为Entry实体
        Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
        //检查key为null则返回NULL_KEY对象
        Object k = maskNull(entry.getKey());
        //取key的hashCode
        int h = hash(k);
        //计算出该key的下标索引
        int i = indexFor(h, tab.length);
         //取索引i处的Entry,用来遍历链表时存放上个节点
        Entry<K,V> prev = tab[i];
        //用来遍历链表时存放当前节点
        Entry<K,V> e = prev;

        //遍历链表
        while (e != null) {
            //取当前节点的下一节点next
            Entry<K,V> next = e.next;
            //根据hashCode值与equals比较双重判断Entry是否为要移除的对象,若是则移除并返回true
            if (h == e.hash && e.equals(entry)) {
                modCount++;
                size--;
                if (prev == e)
                    tab[i] = next;
                else
                    prev.next = next;
                return true;
            }
            //若不是则继续遍历
            prev = e;
            e = next;
        }

        return false;
    }
//清空WeakHashMap
 public void clear() {
        // clear out ref queue. We don't need to expunge entries
        // since table is getting cleared.
        //清空queue,将queue中的元素一个个取出
        while (queue.poll() != null)
            ;

        modCount++;
        //通过Arrays.fill把底层数组所有元素全部清空
        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.
        //将数组清空后可能引发了GC导致queue中又添加了元素,再次清空
        while (queue.poll() != null)
            ;
    }

//判断WeakHashMap中是否包含value
 public boolean containsValue(Object value) {
         //如果value为空,调用是否包含空value逻辑
        if (value==null)
            return containsNullValue();
         //获取Entry数组,获取之前先删除所有需要被移除的Entry
        Entry<K,V>[] tab = getTable();
         //遍历
        for (int i = tab.length; i-- > 0;)
         //遍历链表
            for (Entry<K,V> e = tab[i]; e != null; e = e.next)
                 //equals比较value值,相同则返回true
                if (value.equals(e.value))
                    return true;
        return false;
    }
//判断是否包含空value
 private boolean containsNullValue() {
        Entry<K,V>[] tab = getTable();
        for (int i = tab.length; i-- > 0;)
            for (Entry<K,V> e = tab[i]; e != null; e = e.next)
                if (e.value==null)
                    return true;
        return false;
    }
//返回WeakHashMap中的所有key的set集合
 public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }
//返回WeakHashMap中的所有value的集合
public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }
//返回WeakHashMap中的所有Entry的set集合
public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }
expungeStaleEntries源码总结:

①:循环遍历引用队列(queue), 如果发现某个对象被GC了,那么就开始处理。
②:如果被处理的这个节点是头节点,那么直接把该节点的下个节点放到头节点,然后帮助GC去除value的引用,接着把WeakHashMap的大小减1。
③:如果被处理的这个节点不是头结点,那么就需要把这个节点的上个节点中的next指针直接指向当前节点的下个节点。意思就是a->b->c,这个时候要移除b,那么就变成a->c。然后帮助GC去除value的引用,接着把WeakHashMap的大小减1。
 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值