WeakHashMap是一個(gè)帶有弱鍵的Map坷剧,即當(dāng)某個(gè)鍵不再正常使用的時(shí)候,這個(gè)鍵就會(huì)被移除喊暖,它所對(duì)應(yīng)的鍵值對(duì)也就被移除了惫企。該類支持空鍵和空值,具有與HashMap相似的性能特征陵叽,有與初始容量和負(fù)載因子狞尔。WeakHashMap是非同步的。
WeakHashMap的定義及說明
定義如下:
public class WeakHashMap<K,V>extends AbstractMap<K,V> implements Map<K,V> {}
WeakHashMap的定義還是比較簡單的巩掺,就是繼承了AbstractMap偏序,實(shí)現(xiàn)了Map。即擁有Map基本的屬性和方法胖替。
構(gòu)造方法
構(gòu)造方法如下:
//最大的容量研儒,如果具有參數(shù)的任一構(gòu)造函數(shù)隱式指定較高值,則使用最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默認(rèn)的初始容量独令,必須是2的次方
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//默認(rèn)的加載因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
//數(shù)組類型端朵,實(shí)際是一個(gè)單鏈表,數(shù)組長度必須是2的次方
Entry<K,V>[] table;
//閾值(容量*loadFaactor)
private int threshold;
//加載因子
private final float loadFactor;
//WeakHashMap中包含的鍵值的數(shù)量
private int size;
//使用給定的初始容量和給定的加載因子構(gòu)造一個(gè)新的空WeakHashMap记焊。
public WeakHashMap(int initialCapacity, float loadFactor) {
//判斷自定義的初始容量
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//判斷自定義的加載因子
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
//計(jì)算初始容量(找到大于initialCapacity的最小的2的次方)
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
//新建一個(gè)空數(shù)組
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
}
//使用給定的初始容量和默認(rèn)加載因子(0.75)構(gòu)造一個(gè)新的空WeakHashMap逸月。
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//使用默認(rèn)初始容量(16)和加載因子(0.75)構(gòu)造一個(gè)新的空WeakHashMap
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//使用與指定映射相同的映射構(gòu)造一個(gè)新的WeakHashMap。 使用默認(rèn)加載因子(0.75)創(chuàng)建WeakHashMap遍膜,且初始容量要足以保存指定映射中的映射碗硬。
public WeakHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}
有沒有一種熟悉的感覺?不錯(cuò)瓢颅,就是HashMap恩尾。可以看到它與HashMap相類似挽懦,都有容量翰意、加載因子、閾值以及都是單鏈表結(jié)構(gòu)信柿。怎么看出是一個(gè)單鏈表冀偶?看一下Entry就了然了:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
//值
V value;
//hash值
final int hash;
//下個(gè)節(jié)點(diǎn)
Entry<K,V> next;
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
//通過WeakReference調(diào)用Reference的方法,使key注冊到queue中渔嚷,并且进鸠,key為弱引用
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
}
其中ReferenceQueue是一個(gè)引用隊(duì)列,在檢測到可到達(dá)性更改后形病,垃圾回收器將已注冊的引用對(duì)象添加到隊(duì)列中客年,主要是用于監(jiān)聽Reference所指向的對(duì)象是否已經(jīng)被垃圾回收。通過"super(key, queue)"方法及其調(diào)用方法漠吻,使key成為一個(gè)弱引用對(duì)象量瓜,并將其注冊到queue中,以便在key被GC的時(shí)候可以添加到queue中去途乃。
源碼簡析
我們還是從put()方法入手绍傲,分析一下它的實(shí)現(xiàn)原理:
//已清除的Entries的引用隊(duì)列
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
public V put(K key, V value) {
//判斷key是否為空
Object k = maskNull(key);
//獲取key經(jīng)過處理后的hashCode
int h = hash(k);
//獲取當(dāng)前(清除了被GC了的key所對(duì)應(yīng)的映射) 的表
Entry<K,V>[] tab = getTable();
//返回哈希碼h在數(shù)組中對(duì)應(yīng)的索引
int i = indexFor(h, tab.length);
//獲取i對(duì)應(yīng)的索引位置的鏈表的首節(jié)點(diǎn)并遍歷鏈表
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
//判斷hash值是否相等
if (h == e.hash && eq(k, e.get())) {
//獲取當(dāng)前位置的舊值
V oldValue = e.value;
//新舊值不相等,則新值覆蓋舊值
if (value != oldValue)
e.value = value;
//返回舊值耍共,結(jié)束后面的操作
return oldValue;
}
}
//修改次數(shù)加1
modCount++;
//將新元素添加到數(shù)組中
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
//調(diào)整數(shù)組大小
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
//對(duì)key的hashCode做處理
final int hash(Object k) {
//獲取k的hashCode
int h = k.hashCode();
//hashCode混淆
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//首次刪除過時(shí)條目后返回表
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
//從表中清除過時(shí)的條目
private void expungeStaleEntries() {
//遍歷queue中的key(已被GC)
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
//取出queue中的當(dāng)前位置的元素
Entry<K,V> e = (Entry<K,V>) x;
//獲取該key在數(shù)組中的所對(duì)應(yīng)的索引
int i = indexFor(e.hash, table.length);
//將當(dāng)前key賦值與prev
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
//遍歷索引i對(duì)應(yīng)的鏈表
while (p != null) {
//獲取p的下一個(gè)元素并賦值給next
Entry<K,V> next = p.next;
//判斷p是否等于e(除了第一次輪詢以外烫饼,其他情況下p = prev.next())
if (p == e) {
//判斷prev是否等于e
if (prev == e)
//p和prev都等于e,則直接將數(shù)組索引為i的鏈表的首節(jié)點(diǎn)替換為next
table[i] = next;
else
//只有p等于e划提,則改變prev的下一個(gè)元素的指向枫弟,將其變?yōu)閜的下一個(gè)元素,即刪除p
prev.next = next;
//不得將e.next歸零; 舊的條目可能被HashIterator使用
//置為空以方便GC回收
e.value = null;
//數(shù)組長度減一
size--;
break;
}
//重新賦值鹏往,以便再次循環(huán)
prev = p;
p = next;
}
}
}
}
//返回哈希碼h對(duì)應(yīng)的索引
private static int indexFor(int h, int length) {
return h & (length-1);
}
//調(diào)整數(shù)組大小
void resize(int newCapacity) {
//獲取當(dāng)前table數(shù)組
Entry<K,V>[] oldTable = getTable();
//獲取當(dāng)前數(shù)組長度
int oldCapacity = oldTable.length;
//判斷數(shù)組長度是否大于最大值
if (oldCapacity == MAXIMUM_CAPACITY) {
//將閾值置為最大值
threshold = Integer.MAX_VALUE;
return;
}
//使用新的數(shù)組長度新建一個(gè)數(shù)組
Entry<K,V>[] newTable = newTable(newCapacity);
//將舊數(shù)組的所有元素移入新數(shù)組
transfer(oldTable, newTable);
//將table指向新數(shù)組
table = newTable;
//判斷數(shù)組長度是否大于等于閾值的1/2
if (size >= threshold / 2) {
//重新計(jì)算閾值
threshold = (int)(newCapacity * loadFactor);
} else {
//從表中清除過時(shí)的條目
expungeStaleEntries();
//將新元素移入舊的數(shù)組
transfer(newTable, oldTable);
//將table指向舊數(shù)組
table = oldTable;
}
}
//將所有條目從src傳輸?shù)絛est
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
for (int j = 0; j < src.length; ++j) {
//獲取src指定位置的元素
Entry<K,V> e = src[j];
src[j] = null;
//遍歷指定索引下的鏈表
while (e != null) {
Entry<K,V> next = e.next;
//獲取key
Object key = e.get();
if (key == null) {
//key為null則將其next和value置為空淡诗,且數(shù)組長度減一
e.next = null;
e.value = null;
size--;
} else {
//獲取e在dest數(shù)組中所對(duì)應(yīng)的索引,并賦值
int i = indexFor(e.hash, dest.length);
e.next = dest[i];
dest[i] = e;
}
//賦值并重新開始循環(huán)
e = next;
}
}
}
從上面的分析中我們可以看出伊履,WeakHashMap每次put一個(gè)元素韩容,都會(huì)先刪除table中不再正常使用的元素。然后再判斷當(dāng)前已有元素中的key是否有與要添加元素的key相同的唐瀑,相同則覆蓋其值群凶,不同則將新元素添加如數(shù)組中。在這個(gè)過程中不再正常使用的鍵的原理為:當(dāng)WeakHashMap中的鍵(弱引用)被GC回收時(shí)哄辣,該鍵會(huì)被添加到queue中请梢。然后赠尾,在put過程中會(huì)執(zhí)行expungeStaleEntries()方法,該方法會(huì)遍歷queue中的key并刪除WeakHashMap中與其key對(duì)應(yīng)的鍵值對(duì)毅弧。
在expungeStaleEntries()方法中气嫁,有一個(gè)queue.poll()方法,它是ReferenceQueue類中的方法够坐,具體如下:
//ReferenceQueue的隊(duì)首
private Reference<? extends T> head = null;
//ReferenceQueue的隊(duì)尾
private Reference<? extends T> tail = null;
//判斷隊(duì)列中是否為空寸宵,如果不為空,則取出鏈表中head位置的元素
public Reference<? extends T> poll() {
synchronized (lock) {
if (head == null)
return null;
return reallyPollLocked();
}
}
//Reference.queueNext將設(shè)置為sQueueNextUnenqueued元咙,以指示何時(shí)將引用放入隊(duì)列并從隊(duì)列中刪除梯影。
private static final Reference sQueueNextUnenqueued = new PhantomReference(null, null);
private Reference<? extends T> reallyPollLocked() {
if (head != null) {
Reference<? extends T> r = head;
if (head == tail) {
tail = null;
head = null;
} else {
head = head.queueNext;
}
//更新queueNext以指示引用已入隊(duì),但現(xiàn)在已從隊(duì)列中刪除庶香。
r.queueNext = sQueueNextUnenqueued;
return r;
}
return null;
}
作用就是取出queue中的key甲棍,以方便從數(shù)組中刪除。
好了脉课,本文到此結(jié)束救军。
感覺自己博客寫的好亂!L攘恪唱遭!后期要整理一下。