JDK源码阅读—WeakHashMap

简介

WeakHashMap是一个散列表,储存的元素为key-value的键值对,键和值允许为null。与其他Map不同的是,WeakHashMap储存的是key的弱引用,当key没有其他引用时,GC后key会被回收,WeakHashMap会将其对应的键值对从Map中删除,这个特性使得WeakHashMap比较适合做缓存。

以下分析基于corretto-1.8.0_282版本。

继承关系

WeakHashMap继承关系.png

  1. 实现Map接口,提供了Map的相关操作。

属性

DEFAULT_INITIAL_CAPACITY

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

MAXIMUM_CAPACITY

/**
 * 最大容量,必须是小于等于2的30次方的一个2的幂
 */
private static final int MAXIMUM_CAPACITY = 1 << 30;

DEFAULT_LOAD_FACTOR

/**
 * 默认负载系数
 */
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

table

/**
 * 桶数组,长度必须始终为2的幂
 */
Entry<K,V>[] table;

size

/**
 * 键值对的数量
 */
private int size;

threshold

/**
 * 扩容阈值,当元素个数超过此阈值时,对桶数组进行扩容
 */
private int threshold;

loadFactor

/**
 * 负载系数
 */
private final float loadFactor;

queue

/**
 * 弱引用的清除队列
 */
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

modCount

/**
 * 修改次数,用在迭代器中进行快速失败检查(fail-fast)
 */
int modCount;

NULL_KEY

/**
 * key为null时的替换对象
 */
private static final Object NULL_KEY = new Object();

重要内部类

Entry

/**
 * 储存元素的节点
 * 继承自WeakReference,对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;
    }

    @SuppressWarnings("unchecked")
    public K getKey() {
        return (K) WeakHashMap.unmaskNull(get());
    }

    public V getValue() {
        return value;
    }

    public V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return Objects.hashCode(k) ^ Objects.hashCode(v);
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}

构造方法

WeakHashMap(int initialCapacity, float loadFactor)

/**
 * 使用给定的初始容量和负载系数实例化WeakHashMap
 */
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);

    // 从1、2、4、8、16...中选择一个大于等于initialCapacity的最小的2的幂
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;

    // 申请数组空间
    table = newTable(capacity);
    this.loadFactor = loadFactor;

    // 计算扩容阈值(当前容量*负载系数)
    threshold = (int)(capacity * loadFactor);
}

@SuppressWarnings("unchecked")
private Entry<K,V>[] newTable(int n) {
    return (Entry<K,V>[]) new Entry<?,?>[n];
}

WeakHashMap(int initialCapacity)

/**
 * 使用给定的初始容量和默认负载系数(0.75)实例化WeakHashMap
 */
public WeakHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

WeakHashMap()

/**
 * 使用默认初始容量(16)和默认负载系数(0.75)实例化WeakHashMap
 */
public WeakHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

WeakHashMap(Map<? extends K, ? extends V> m)

/**
 * 使用给定的Map实例化WeakHashMap,容量根据传入Map的大小计算,负载系数为默认(0.75)
 * 并将传入Map中的所有键值对添加进WeakHashMap中
 */
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)

/**
 * 将键值对添加进Map,或者key之前存在,则将其对应的值更新为value
 */
public V put(K key, V value) {
    // 若key为null,将其替换为NULL_KEY
    Object k = maskNull(key);
    // 对key的hashCode进行扰动
    int h = hash(k);
    // 清理已被回收的元素
    Entry<K,V>[] tab = getTable();
    // 找到hash对应的索引
    int i = indexFor(h, tab.length);

    // 遍历链表
    for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
        // 如果key之前存在,且保存的值不等于value,将其值更新为value
        if (h == e.hash && eq(k, e.get())) {
            V oldValue = e.value;
            if (value != oldValue)
                e.value = value;
            return oldValue;
        }
    }

    // key之前不存在,创建一个新的节点,将其插入链表头部
    modCount++;
    Entry<K,V> e = tab[i];
    tab[i] = new Entry<>(k, value, queue, h, e);

    // 若插入后元素个数大于等于扩容阈值,将桶的容量扩大为2倍
    if (++size >= threshold)
        resize(tab.length * 2);
    return null;
}

/**
 * 当key为null时,将key替换成NULL_KEY对象
 */
private static Object maskNull(Object key) {
    return (key == null) ? NULL_KEY : key;
}

/**
 * 扰动函数
 */
final int hash(Object k) {
    int h = k.hashCode();

    // 对hashCode进行4次扰动
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

/**
 * 找到hash在桶数组中对应的索引
 */
private static int indexFor(int h, int length) {
    // 当length为2的幂时,相当于对length取模
    return h & (length-1);
}

private static boolean eq(Object x, Object y) {
    return x == y || x.equals(y);
}

putAll(Map<? extends K, ? extends V> m)

/**
 * 将给定Map中的所有元素拷贝到WeakHashMap中
 */
public void putAll(Map<? extends K, ? extends V> m) {

    // 若传入Map为空,则不进行处理
    int numKeysToBeAdded = m.size();
    if (numKeysToBeAdded == 0)
        return;

    // 若传入map大小超过扩容阈值,对桶进行扩容
    if (numKeysToBeAdded > threshold) {

        // 计算新容量
        int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);

        // 新容量不能超过最大容量
        if (targetCapacity > MAXIMUM_CAPACITY)
            targetCapacity = MAXIMUM_CAPACITY;
        int newCapacity = table.length;

        // 保证新容量为2的幂
        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());
}

resize(int newCapacity)

/**
 * 对桶数组进行扩容,并将元素复制到新的桶中
 */
void resize(int newCapacity) {
    Entry<K,V>[] oldTable = getTable();
    int oldCapacity = oldTable.length;

    // 如果旧容量已经到最大容量,则不对桶进行扩容
    // 将扩容阈值设置为Integer.MAX_VALUE
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    // 创建一个新的数组
    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.
        */
    if (size >= threshold / 2) {
        // 如果当前元素个数不小于(threshold / 2),重新计算threshold
        threshold = (int)(newCapacity * loadFactor);
    } else {
        // 如果回收了元素之后,元素个数低于(threshold / 2),则回复旧桶
        expungeStaleEntries();
        transfer(newTable, oldTable);
        table = oldTable;
    }
}

/**
 * 将旧数组中的所有元素转移至新数组
 */
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) {
                // 若key已被回收,则删除此节点
                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;
        }
    }
}

expungeStaleEntries()

/**
 * 清除已经被回收的键值对
 */
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相同的节点
                if (p == e) {
                    if (prev == e)
                        // 如果是链表头,将链表头指针指向下一个节点
                        table[i] = next;
                    else
                        // 将前驱节点和后继节点链接起来,删除当前节点
                        prev.next = next;

                    e.value = null;
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

remove(Object key)

/**
 * 删除给定的key
 */
public V remove(Object key) {
    // 若key为null,将其替换为NULL_KEY
    Object k = maskNull(key);
    // 对key的hashCode进行扰动
    int h = hash(k);
    // 清理已被回收的元素
    Entry<K,V>[] tab = getTable();
    // 找到hash对应的索引
    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;

        // 如果此节点key与给定key相同,则将其删除
        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;
}

clear()

/**
 * 清空Map
 */
public void clear() {
    // 清空引用队列
    while (queue.poll() != null)
        ;

    // 修改次数加一
    modCount++;

    // 桶数组所有位置都置为null
    Arrays.fill(table, null);

    // 元素个数置为0
    size = 0;

    // 清空数组后可能有GC回收,再次清空引用队列
    while (queue.poll() != null)
        ;
}

get(Object key)

/**
 * 若key还存在,则返回其对应的value值
 */
public V get(Object key) {
    // 若key为null,将其替换为NULL_KEY
    Object k = maskNull(key);
    // 对key的hashCode进行扰动
    int h = hash(k);
    // 清理已被回收的元素
    Entry<K,V>[] tab = getTable();
    // 找到hash对应的索引
    int index = indexFor(h, tab.length);
    // 遍历链表
    Entry<K,V> e = tab[index];
    while (e != null) {
        // 如果找到key相同的节点,返回其value值
        if (e.hash == h && eq(k, e.get()))
            return e.value;
        e = e.next;
    }
    // 找不到返回null
    return null;
}

containsKey(Object key)

/**
 * 检查是否包含key
 */
public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

/**
 * 根据key查找对应的节点,与get(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;
}

containsValue(Object value)

/**
 * 检查Map是否包含指定value值
 */
public boolean containsValue(Object value) {
    // value为null,跳转至containsNullValue方法
    if (value==null)
        return 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 (value.equals(e.value))
                return true;
    return false;
}

containsNullValue()

/**
 * 是否包含value为null的键值对
 */
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;
}

forEach(BiConsumer<? super K, ? super V> action)

/**
 * 遍历所有元素
 */
@SuppressWarnings("unchecked")
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    int expectedModCount = modCount;

    Entry<K, V>[] tab = getTable();
    // 遍历桶数组
    for (Entry<K, V> entry : tab) {
        // 遍历链表
        while (entry != null) {
            Object key = entry.get();

            // 若key不为null,传给action处理
            if (key != null) {
                action.accept((K)WeakHashMap.unmaskNull(key), entry.value);
            }
            entry = entry.next;

            if (expectedModCount != modCount) {
                throw new ConcurrentModificationException();
            }
        }
    }
}

replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

/**
 * 遍历所有元素,根据给定的function替换值
 * @param function
 */
@SuppressWarnings("unchecked")
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
    Objects.requireNonNull(function);
    int expectedModCount = modCount;

    Entry<K, V>[] tab = getTable();;
    // 遍历桶数组
    for (Entry<K, V> entry : tab) {
        // 遍历链表
        while (entry != null) {
            Object key = entry.get();
            // 若key不为null,传递给function,将返回的新值赋给value
            if (key != null) {
                entry.value = function.apply((K)WeakHashMap.unmaskNull(key), entry.value);
            }
            entry = entry.next;

            if (expectedModCount != modCount) {
                throw new ConcurrentModificationException();
            }
        }
    }
}

总结

  1. 与HashMap类似,WeakHashMap默认初始容量16,默认负载系数0.75,每次扩容,桶数组容量变为原来的2倍。
  2. WeakHashMap中只使用拉链法解决哈希冲突,冲突加剧时也不会转化为红黑树。
  3. WeakHashMap只保存了key的弱引用,如果key没有其他引用的话,会在GC时被回收,所以即使没有对WeakHashMap进行操作,多次调用size()、get()等方法时,返回结果也可能不同。
  4. WeakHashMap内部使用一个静态对象NULL_KEY来对key=null时的key进行替换,遍历时会再将其转换回null。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值