介绍
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。