简介
WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。
继承体系
可见,WeakHashMap没有实现Clone和Serializable接口,所以不具有克隆和序列化的特性。
存储结构
WeakHashMap因为gc的时候会把没有强引用的key回收掉,所以注定了它里面的元素不会太多,因此也就不需要像HashMap那样元素多的时候转化为红黑树来处理了。
因此,WeakHashMap的存储结构只有(数组 + 链表)。
源码解析
属性
1 | /** |
(1)容量
容量为数组的长度,亦即桶的个数,默认为16,最大为2的30次方,当容量达到64时才可以树化。
(2)装载因子
装载因子用来计算容量达到多少时才进行扩容,默认装载因子为0.75。
(3)引用队列
当弱键失效的时候会把Entry添加到这个队列中,当下次访问map的时候会把失效的Entry清除掉。
Entry内部类
WeakHashMap内部的存储节点, 没有key属性。
1 | private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { |
从Entry的构造方法我们知道,key和queue最终会传到到Reference的构造方法中,这里的key就是Reference的referent属性,它会被gc特殊对待,即当没有强引用存在时,当下一次gc的时候会被清除。
构造方法
1 | public WeakHashMap(int initialCapacity, float loadFactor) { |
构造方法与HashMap基本类似,初始容量为大于等于传入容量最近的2的n次方,扩容门槛threshold等于capacity * loadFactor。
put(K key, V value)方法
添加元素的方法。
1 | public V put(K key, V value) { |
(1)计算hash;
这里与HashMap有所不同,HashMap中如果key为空直接返回0,这里是用空对象来计算的。
另外打散方式也不同,HashMap只用了一次异或,这里用了四次,HashMap给出的解释是一次够了,而且就算冲突了也会转换成红黑树,对效率没什么影响。
(2)计算在哪个桶中;
(3)遍历桶对应的链表;
(4)如果找到元素就用新值替换旧值,并返回旧值;
(5)如果没找到就在链表头部插入新元素;
HashMap就插入到链表尾部。
(6)如果元素数量达到了扩容门槛,就把容量扩大到2倍大小;
HashMap中是大于threshold才扩容,这里等于threshold就开始扩容了。
resize(int newCapacity)方法
扩容方法。
1 | void resize(int newCapacity) { |
(1)判断旧容量是否达到最大容量;
(2)新建新桶并把元素全部转移到新桶中;
(3)如果转移后元素个数不到扩容门槛的一半,则把元素再转移回旧桶,继续使用旧桶,说明不需要扩容;
(4)否则使用新桶,并计算新的扩容门槛;
(5)转移元素的过程中会把key为null的元素清除掉,所以size会变小;
get(Object key)方法
获取元素。
1 | public V get(Object key) { |
(1)计算hash值;
(2)遍历所在桶对应的链表;
(3)如果找到了就返回元素的value值;
(4)如果没找到就返回空;
remove(Object key)方法
移除元素。
1 | public V remove(Object key) { |
(1)计算hash;
(2)找到所在的桶;
(3)遍历桶对应的链表;
(4)如果找到了就删除该节点,并返回该节点的value值;
(5)如果没找到就返回null;
expungeStaleEntries()方法
剔除失效的Entry。
1 | private void expungeStaleEntries() { |
(1)当key失效的时候gc会自动把对应的Entry添加到这个引用队列中;
(2)所有对map的操作都会直接或间接地调用到这个方法先移除失效的Entry,比如getTable()、size()、resize();
(3)这个方法的目的就是遍历引用队列,并把其中保存的Entry从map中移除掉,具体的过程请看类注释;
(4)从这里可以看到移除Entry的同时把value也一并置为null帮助gc清理元素,防御性编程。
使用案例
说了这么多,不举个使用的例子怎么过得去。
1 | package com.coolcoding.code; |
在这里通过new String()声明的变量才是弱引用,使用”6”这种声明方式会一直存在于常量池中,不会被清理,所以”6”这个元素会一直在map里面,其它的元素随着gc都会被清理掉。
总结
(1)WeakHashMap使用(数组 + 链表)存储结构;
(2)WeakHashMap中的key是弱引用,gc的时候会被清除;
(3)每次对map的操作都会剔除失效key对应的Entry;
(4)使用String作为key时,一定要使用new String()这样的方式声明key,才会失效,其它的基本类型的包装类型是一样的;
(5)WeakHashMap常用来作为缓存使用;
彩蛋
强、软、弱、虚引用知多少?
(1)强引用
使用最普遍的引用。如果一个对象具有强引用,它绝对不会被gc回收。如果内存空间不足了,gc宁愿抛出OutOfMemoryError,也不是会回收具有强引用的对象。
(2)软引用
如果一个对象只具有软引用,则内存空间足够时不会回收它,但内存空间不够时就会回收这部分对象。只要这个具有软引用对象没有被回收,程序就可以正常使用。
(3)弱引用
如果一个对象只具有弱引用,则不管内存空间够不够,当gc扫描到它时就会回收它。
(4)虚引用
如果一个对象只具有虚引用,那么它就和没有任何引用一样,任何时候都可能被gc回收。
软(弱、虚)引用必须和一个引用队列(ReferenceQueue)一起使用,当gc回收这个软(弱、虚)引用引用的对象时,会把这个软(弱、虚)引用放到这个引用队列中。
比如,上述的Entry是一个弱引用,它引用的对象是key,当key被回收时,Entry会被放到queue中。