WeakHashMap类
在前面,我们了解了许多Map接口下的集合,而WeakHashMap从名字来看,它依旧与Map有关。不过它名字前面还有一个Weak,我们就更能自然而然的想到这里面还牵扯到一种弱引用结构,因此想要彻底搞懂,我们还需要知道四种引用。详细介绍请参考链接:Java中的四种引用。
Java的四种引用
在JVM中,一个对象如果不再被使用就会被当做垃圾给回收掉,判断一个对象是否是垃圾,通常有两种方法:引用计数法和可达性分析法。不管是哪一种方法判断一个对象是否是垃圾的条件总是一个对象的引用是都没有了。
JDK1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用、软引用、弱引用、虚引用4 种。而我们的WeakHashMap就是基于弱引用。
引用 | 简介 |
---|---|
强引用 | 如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出OutOfMemoryError 错误,使程序异常终止。比如String str = new String("hello"); 这时候str就是一个强引用。 |
软引用 | 内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。 |
弱引用 | 如果一个对象具有弱引用,在垃圾回收时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。 |
虚引用 | 如果一个对象具有虚引用,就相当于没有引用,在任何时候都有可能被回收。使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。 |
WeakHashMap是基于弱引用的,也就是说只要垃圾回收机制一开启,就直接开始了扫荡,看见了就清除。
我们接下来先看看WeakHashMap和HashMap究竟有什么样的区别。
WeakHashMap简介
WeakHashMap的继承关系如下:
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V> {
...
}
从上面可以看出,WeakHashMap 继承于AbstractMap,实现了Map接口。和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理大致上就是通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
-
1、新建WeakHashMap,将“键值对”添加到WeakHashMap中。
- 实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
-
2、当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
-
3、当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。
WeakHashMap的继承结构
WeakHashMap的源码分析
1、重要的成员变量
Entry<K,V>[] table;
private int size;
private int threshold;
private final float loadFactor;
int modCount;
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
table
是一个Entry[] 数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。size
是Hashtable的大小,它是Hashtable保存的键值对的数量。threshold
是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=“容量*加载因子”。loadFactor
就是加载因子。modCount
是用来实现fail-fast机制的.queue
保存的是“已被GC清除”的“弱引用的键”。
2、WeakHashMap的构造函数
WeakHashMap共有4个构造函数,如下:
{
// 默认构造函数。
WeakHashMap()
// 指定“容量大小”的构造函数
WeakHashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数
WeakHashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数
WeakHashMap(Map<? extends K, ? extends V> map)
}
3、WeakHashMap的API
void clear()
Object clone()
boolean containsKey(Object key)
boolean containsValue(Object value)
Set<Entry<K, V>> entrySet()
V get(Object key)
boolean isEmpty()
Set<K> keySet()
V put(K key, V value)
void putAll(Map<? extends K, ? extends V> map)
V remove(Object key)
int size()
Collection<V> values()
- WeakHashMap的方法与HashMap方法操作基本相似,这里就不做分析了。
4、WeakHashMap的结点结构
/**
* 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;
}
}
- 从这里我们可以看到其内部的Entry继承了WeakReference,也就是弱引用,所以就具有了弱引用的特点。不过还要注意一点,那就是ReferenceQueue,他的作用是GC会清理掉对象之后,引用对象会被放到ReferenceQueue中。
WeakHashMap和HashMap的对比
相同点
WeakHashMap的使用方法、基本特点、底层数据结构均与HashMap相同。
不同点
1、WeakHashMap只实现了Map接口,没有实现clone接口和Serizaseable,因此不能使用克隆方法,不能进行序列化。
2、对key
值的处理不同。
/**
* Value representing null keys inside tables.
*/
private static final Object NULL_KEY = new Object();
/**
* Use NULL_KEY for key if it is null.
*/
private static Object maskNull(Object key) {
return (key == null) ? NULL_KEY : key;
}
- 如上,WeakHashMap和HashMap在插入数据时对
key
值均计算了其哈希值,并进行了扰动处理。不过,WeakHashMap并不是将key=null
的键值对之间存入0下标处,而是用Object
对象替换了null
值,也就是说,当key
为空时,并不是直接存储null
值,而是以Object
对象进行哈希计算和存储。 - 需要注意的是,在打印结果时,依旧打印的是
null
值,原因是在打印时,WeakHashMap将Object
对象换回了null
值。
3、存储key
的引用类型不同,这与Java的四种引用有关,WeakHashMap里存储的均是弱引用对象。
WeakHashMap的使用
public class WeakHashMapTest {
public static void main(String[] args) {
WeakHashMap<String, Integer> map = new WeakHashMap<>();
String s1 = new String("abc");
String s2 = new String("bcd");
String s3 = new String("def");
map.put(s1,123);
map.put(s2,456);
map.put(s3,789);
map.put(null,159); //以Object对象形式存储
map.put(s1,753); //会用新值替换旧值
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
//调用GC
System.gc();
System.out.println("*******************调用GC之后********************");
Iterator<Map.Entry<String, Integer>> iterator1 = map.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry<String, Integer> next = iterator1.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
}
}
运行结果:
key:s3 value:789
key:null value:159
key:s1 value:753
key:s2 value:456
*******************调用GC之后********************
key:s3 value:789
key:null value:159
key:s1 value:753
key:s2 value:456
我们可以发现,在调用GC之后,WeakHashMap里面的对象并没有被回收,这时为什么呢?
- 原因就是:在传递
key
值String时,我们使用了new String()
使得该对象为强引用,当一个对象即被强引用,又被弱引用时,调用GC是,不会被垃圾回收的。
public class WeakHashMapTest {
public static void main(String[] args) {
WeakHashMap<String, Integer> map = new WeakHashMap<>();
String s1 = new String("abc");
String s2 = new String("bcd");
String s3 = new String("def");
map.put(s1,123);
map.put(s2,456);
map.put(s3,789);
map.put(null,159); //以Object对象形式存储
map.put(s1,753); //会用新值替换旧值
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
s1 = null;
s2 = null;
//调用GC
System.gc();
System.out.println("*******************调用GC之后********************");
Iterator<Map.Entry<String, Integer>> iterator1 = map.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry<String, Integer> next = iterator1.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
}
}
运行结果:
key:bcd value:456
key:null value:159
key:def value:789
key:abc value:753
*******************调用GC之后********************
key:null value:159
key:def value:789
- 可以看到,当我们在调用GC之前,如果调用
s1 = null;s2 = null;
就相当于不再强引用该对象,那么此时该对象仅剩下弱引用,在调用GC后,就会被当作垃圾进行回收。 - 但是,以如下代码为例:
public class WeakHashMapTest {
public static void main(String[] args) {
WeakHashMap<String, Integer> map = new WeakHashMap<>();
map.put("abc",123);
map.put("bcd",456);
map.put("def",789);
map.put(null,159); //以Object对象形式存储
map.put("abc",753); //会用新值替换旧值
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
//调用GC
System.gc();
System.out.println("*******************调用GC之后********************");
Iterator<Map.Entry<String, Integer>> iterator1 = map.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry<String, Integer> next = iterator1.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
}
}
运行结果:
key:bcd value:456
key:null value:159
key:def value:789
key:abc value:753
*******************调用GC之后********************
key:bcd value:456
key:null value:159
key:def value:789
key:abc value:753
- 结果显示,依然没有被垃圾回收,在我看来,其原因应该是传入的字符串存入到了常量池中,而堆上对常量池中的字符串进行了引用拷贝,因此,其本身依然存在强引用,所以并没有进行垃圾回收。
- 这就相当于向WeakHashMap中存放
int
类型的值时,会被封装为Integer类型,然而Integer保留了-128到127的缓存。但是对于int来说范围大很多,因此如果此时调用GC,那些Key <= 127的Entry将不会进行自动回收,但是那些大于127的将会被回收,因此最后的值总是会稳定在128左右。