所使用的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}