Java中的Map【九】WeakHashMap类
所使用的jdk版本为1.8.0_172版本,先看一下 WeakHashMap<K,V> 在JDK中Map的UML类图中的主要继承实现关系:
概述
WeakHashMap 是基于 弱引用(WeakReference)类型实现的。 在 WeakHashMap 中,对键K的引用是弱引用类型,当某个键不再正常使用,比如只被弱引用关联时,我们知道此时垃圾回收器会回收该键,此时WeakHashMap将自动移除该键对应的映射条目。null 值和 null 键都被支持。该类具有与 HashMap 类相似的性能特征,并具有相同的效能参数初始容量 和加载因子。WeakHashMap 的实现是不同步的,即线程不安全的。
示例如下:
public static void main(String[] args) {
//weakHashMap存储 学生-分数 映射
WeakHashMap<Student, Integer> weakHashMap = new WeakHashMap<>();
//小明和小华对象分别有强引用关联:xiaoMing 和 xiaoHua;小亮直接new的对象,没有强引用关系
Student xiaoMing = new Student("小明", 9);
Student xiaoHua = new Student("小华",8);
weakHashMap.put(xiaoMing, 100);
weakHashMap.put(xiaoHua, 86);
weakHashMap.put(new Student("小亮",9), 59);
System.out.println("GC 前:");
System.out.println(weakHashMap);
System.gc();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("GC 后:");
System.out.println(weakHashMap);
}
public static class Student {
private String name;
private Integer age;
public Student(String name, Integer age){
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
public Integer getAge(){
return this.age;
}
@Override
public int hashCode() {
return name.hashCode() ^ age.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Student) {
return name.equals(((Student) obj).getName()) && age.equals(((Student) obj).getAge());
}
return false;
}
@Override
public String toString() {
return "{name:"+name+",age:"+age+"}";
}
}
程序运行结果:
GC 前:
{{name:小明,age:9}=100, {name:小华,age:8}=86, {name:小亮,age:9}=59}
GC 后:
{{name:小明,age:9}=100, {name:小华,age:8}=86}
new 出的小亮学生对象,没有强引用关联,只有weakHashMap 对它的弱引用,当GC时,小亮对象会被回收,weakHashMap 会回收小亮对象键对应的映射关系。
数据结构
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V> {
WeakHashMap 底层又其内部类节点数组Entry<K,V>[] table 和Entry<K,V> 链表实现的,另有队列ReferenceQueue queue 保存GC回收键的弱引用对象,用来清除映射。
Entry<K,V> 类继承了WeakReference类,与其它Map实现最大的不同,就是把对键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) {
//给键 Key 对象创建一个新的的弱引用关联,非强引用关联
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
实现原理
put(K key, V value) 方法
public V put(K key, V value) {
//如果key是null的话,使用一个空Object对象代替
Object k = maskNull(key);
//扰动函数计算hash值
int h = hash(k);
//getTable()方法中清除过时的key(已被GC回收的key)对应的映射关系
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;
}
getTable() 方法
private Entry<K,V>[] getTable() {
// 清除过时的key对应的映射
expungeStaleEntries();
return table;
}
expungeStaleEntries() 方法
/**
* Expunges stale entries from the table.
* 删除被GC回收键对应的映射关系
*/
private void expungeStaleEntries() {
//从ReferenceQueue队列中获取被回收的键
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;
//遍历链表结构,找到被回收的键x,单链表删除节点
while (p != null) {
Entry<K,V> next = p.next;
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
// value引用设为null,帮助GC回收
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
expungeStaleEntries() 方法在扩容resize()、get(Object key)、size()等方法中都有调用。
注意点:
WeakHashMap 中的每个键对象间接地存储为一个弱引用的指示对象。因此,不管是在映射内还是在映射之外,只有在垃圾回收器清除某个键的弱引用之后,该键才会自动移除。
实现注意事项:WeakHashMap 中的值对象由普通的强引用保持。因此应该小心谨慎,确保值对象不会直接或间接地强引用其自身的键,因为这会阻止键的丢弃。注意,值对象可以通过 WeakHashMap 本身间接引用其对应的键;这就是说,某个值对象可能强引用某个其他的键对象,而与该键对象相关联的值对象转而强引用第一个值对象的键。处理此问题的一种方法是,在插入前将值自身包装在 WeakReferences 中,如:m.put(key, new WeakReference(value)),然后,分别用 get 进行解包。
题外:
分析以下程序的输出结果是什么效果:
public static void test1() {
String four = "four", five = "five";
WeakHashMap<String,Integer> weakHashMap1 = new WeakHashMap<>();
weakHashMap1.put(four, 4);
weakHashMap1.put(five, 5);
weakHashMap1.put("six", 6);
WeakHashMap<String,Integer> weakHashMap2 = new WeakHashMap<>();
weakHashMap2.put(four, 4);
weakHashMap2.put(five, 5);
weakHashMap2.put(new String("six"), 6);
System.out.println("GC 前:");
System.out.println(weakHashMap1);
System.out.println(weakHashMap2);
System.gc();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("GC 后:");
System.out.println(weakHashMap1);
System.out.println(weakHashMap2);
}
程序运行结果:
GC 前:
{six=6, four=4, five=5}
{six=6, four=4, five=5}
GC 后:
{six=6, four=4, five=5}
{four=4, five=5}
上一篇: 窗口机制的笔记
推荐阅读
-
Java中的Map【九】WeakHashMap类
-
Java中的WeakHashMap
-
一篇看懂Java中的Unsafe类
-
java中Object类的equals理解
-
js中实现java中的Map对象 博客分类: web
-
js中实现java中的Map对象 博客分类: web
-
Java日期时间API系列10-----Jdk8中java.time包中的新的日期时间API类的DateTimeFormatter
-
定义两个接口,其中各包括一个抽象方法分别用来完成两个数的加法和减法操作,然后创建一个类KY6_3来实现这两个接口中的抽象方法。编写程序KY6_3.java,将源程序写在实验报告中。
-
JAVA 将一个StringBuffer类对象中的所有小写字母变为大写字母,大写字母变为小写字母,然后输出显示
-
荐 Java——集合中的Map接口通过HashMap类实现一些常用的方法