简介
WeakHashMap是一个散列表,储存的元素为key-value的键值对,键和值允许为null。与其他Map不同的是,WeakHashMap储存的是key的弱引用,当key没有其他引用时,GC后key会被回收,WeakHashMap会将其对应的键值对从Map中删除,这个特性使得WeakHashMap比较适合做缓存。
以下分析基于corretto-1.8.0_282版本。
继承关系
- 实现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();
}
}
}
}
总结
- 与HashMap类似,WeakHashMap默认初始容量16,默认负载系数0.75,每次扩容,桶数组容量变为原来的2倍。
- WeakHashMap中只使用拉链法解决哈希冲突,冲突加剧时也不会转化为红黑树。
- WeakHashMap只保存了key的弱引用,如果key没有其他引用的话,会在GC时被回收,所以即使没有对WeakHashMap进行操作,多次调用size()、get()等方法时,返回结果也可能不同。
- WeakHashMap内部使用一个静态对象NULL_KEY来对key=null时的key进行替换,遍历时会再将其转换回null。