总结
WeakHashMap 实现了Map接口,但是它与HashMap的区别是 key为WeakReference类型,当key中的对象不再是强引用或者软引用时,垃圾收集器将key自动回收。
WeakHashMap支持null key和null value
WeakHashMap的应用场景
由于垃圾收集器回收一个对象时,通过对象可达性分析算法进行回收。如果我们创建了一个HashMap,HashMap中存放了对象。除非把HashMap对象回收掉,否则HashMap中的对象将不会被回收,根据对象可达性原则,因为HashMap中引用了此对象。在一个大的Hash表中,将会浪费内存。
应用例子:
JDK ThreadLocal 实现
具体的源码不在详细的分析。ThreadLocal是一个map结构,每个Thread对象都有一个ThreadLocalMap对象,用来存放此线程的本地变量,其中map中的key为ThreadLocal,value为需要存放的值。hash冲突使用线性探测的方式。
当本地创建的ThreadLoal的强引用不可达时,虚拟机在进行垃圾回收时把这些对象自动回收,然后放入Queue中,通过遍历queue移除map中key为null的entry,节约了不必要的内存浪费。/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
源码解读
WeakHashMap继承了抽象类AbstractMap 并实现了Map接口。
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
WeakHashMap实例字段及类字段
默认容量 16
private static final int DEFAULT_INITIAL_CAPACITY = 16;
最大容量 必须是2的幂 默认1<<30
private static final int MAXIMUM_CAPACITY = 1 << 30;
负载因子 默认值是0.75 (负载因子跟Hash冲突有关,因子越大,冲突就越大)
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
底层数据结构(数组+链表);数组长度必须是2的幂
Entry<K,V>[] table;
map中实际元素的数量
private int size;
当下次动态扩容时map中元素数量
private int threshold;
负载因子
private final float loadFactor;
虚引用队列。队列中存放被垃圾收集器回收的Entry中的key
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
记录map修改次数的字段(此字段主要用于标识在迭代map时,如果map结构被改变,那么它将出现fail-fast错误,抛出ConcurrentModificationException)
int modCount;
底层数据结构实体类(是一个链表结构)
/**
* The entries in this hash table extend WeakReference, using its main ref
* field as the 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;
}
移除被垃圾回收的对象
由于WeakHashMap的key为弱引用,value为强引用。在创建key的时候注册了ReferenceQueue,因此被垃圾回收key的存放到ReferenceQueue中。通过遍历Queue,把queue对应的key在map中删除。由于queue与map中的key是同一个对象,所以使用内存地址的比较,一定使用==
/**
*实现思路:
*1.从队列里poll()元素,直到队列为空
*2.hash 到table中对应的元素
*3.由于数组里的元素是一个链表结构。当需要删除的元素是头元素
*是,把table[i]设置为头元素的下一个元素。即table[i]=p
*否则:直接删除。即pre.next =p.next。并把value设置为null
*/
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为null,但是内存地址
* 没变,因此使用==比较。这里涉及到equals
* 与==的区别。
* == 的用法:1.对于基本数据类型,是值比
* 较。对于引用对象类型,使用的是内存地址
* equals:引用对象的值比较。
*/
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的值
由于key为null代表着被垃圾收集器进行了回收。因此WeakHashMap中当key为null时,它实际上在集合中存放的是一个Object的空对象。
/**
* Value representing null keys inside tables.
*/
private static final Object NULL_KEY = new Object();
put方法实现
先判断key是否为null,如果为null,则转变为Object.
然后进行依据key得到hash值,定位到table,由于table中的元素是一个链表结构。因此对链表进行遍历。
public V put(K key, V value) {
Object k = maskNull(key);
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) {
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);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}