介紹
WeakHashMap 繼承於AbstractMap,實現了Map接口。
和HashMap一樣,WeakHashMap 也是一個散列表,它存儲的內容也是鍵值對(key-value)映射,而且鍵和值都可以是null。
不過WeakHashMap的鍵是“弱鍵”。在 WeakHashMap 中,當某個鍵不再正常使用時,會被從WeakHashMap中被自動移除。更精確地說,對於一個給定的鍵,其映射的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成爲可終止的,被終止,然後被回收。某個鍵被終止時,它對應的鍵值對也就從映射中有效地移除了。
這個“弱鍵”的原理呢?大致上就是,通過WeakReference和ReferenceQueue實現的。 WeakHashMap的key是“弱鍵”,即是WeakReference類型的;ReferenceQueue是一個隊列,它會保存被GC回收的“弱鍵”。
成員變量
/**
* 默認初始容量是16,必須是2的冪
*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默認加載因子
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 存儲數據的Entry數組,長度是2的冪。
*/
Entry<K,V>[] table;
/**
* WeakHashMap的大小,它是WeakHashMap保存的鍵值對的數量
*/
private int size;
/**
* WeakHashMap的閾值,用於判斷是否需要調整WeakHashMap的容量(threshold = 容量*加載因子).
*/
private int threshold;
/**
*加載因子實際大小
*/
private final float loadFactor;
/**
* queue保存的是“已被GC清除”的“弱引用的鍵”。
*/
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/**
* WeakHashMap被改變的次數
*/
int modCount;
構造函數
//指定容量大小和加載因子的構造函數
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);
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
}
/**
指定容量大小的構造函數
*/
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
默認構造函數
*/
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
包含子map的構造函數
*/
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);
}
核心函數
//根據key獲取value
public V get(Object key) {
//檢查key爲null則返回NULL_KEY對象
Object k = maskNull(key);
//取key的hashCode
int h = hash(k);
//通過getTable獲取數組,去除所有需要被回收的Entry
Entry<K,V>[] tab = getTable();
//計算出該key的下標索引
int index = indexFor(h, tab.length);
//獲取該索引位置的鏈表頭
Entry<K,V> e = tab[index];
////遍歷鏈表,根據hashCode值與equals比較雙重判斷獲取value
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}
//判斷WeakHashMap中是否包含key
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
//根據key獲取Entry實體
Entry<K,V> getEntry(Object key) {
//檢查key爲null則返回NULL_KEY對象
Object k = maskNull(key);
//獲取key的hashCode
int h = hash(k);
//通過getTable獲取數組,去除所有需要被回收的Entry
Entry<K,V>[] tab = getTable();
//計算出該key的下標索引
int index = indexFor(h, tab.length);
//獲取該索引位置的鏈表頭
Entry<K,V> e = tab[index];
//遍歷鏈表,根據hashCode值與equals比較雙重判斷獲取Entry
while (e != null && !(e.hash == h && eq(k, e.get())))
e = e.next;
return e;
}
//將k-v添加到映射關係中
public V put(K key, V value) {
//判斷key是否爲null,如果爲null,new Object個對象
Object k = maskNull(key);
//獲取hash值
int h = hash(k);
//通過getTable獲取數據,去除所有需要被回收的Entry
Entry<K,V>[] tab = getTable();
//獲取到key的下標索引
int i = indexFor(h, tab.length);
//遍歷鏈表,根據hashCode值與equals比較雙重判斷key是否存在,存在則將value替換,並直接返回舊值
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;
}
}
//若key不存在,則將該節點插入鏈表頭,這裏和HashMap不太一樣,HashMap是尾部追加
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
//添加元素後的WeakHashMap大小如果大於等於擴容閾值,則擴容1倍
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
//刪除所有被GC回收的Entry
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {//存在對象被GC,那麼就需要移除map中對應的數據
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;
while (p != null) {//如果p節點存在
Entry<K,V> next = p.next;//定義next節點指向p的下個節點
if (p == e) {//如果p就是當前節點
if (prev == e)
table[i] = next;//桶中第一個數據就是移除的,直接把第二個節點放到節點的位置
else
prev.next = next;//把上一個節點的下個節點指向p後面的節點
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;//減少WeakHashMap的大小
break;//結束
}
prev = p;
p = next;
}
}
}
}
//核心擴容方法
void resize(int newCapacity) {
//獲取原Entry數組,獲取之前先刪除所有需要移除的Entry
Entry<K,V>[] oldTable = getTable();
//獲取原Entry數組的容量
int oldCapacity = oldTable.length;
//如果原數組的容量已經達到最大值2^30,則停止擴容並防止再次擴容
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//實例化新Entry數組
Entry<K,V>[] newTable = newTable(newCapacity);
//將原數組中的元素全部放入新數組
transfer(oldTable, newTable);
//將底層數組替換爲新數組
table = newTable;
/*
* If ignoring null elements and processing ref queue caused massive
* shrinkage, then restore old table. This should be rare, but avoids
* unbounded expansion of garbage-filled tables.
*/
//如果忽略空元素並處理ref隊列導致大量收縮,則恢復舊錶。這應該是很少見的,但是避免了垃圾填充表的無限制擴展
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
//將src數組中的元素全部放入dest數組
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
//遍歷src數組
for (int j = 0; j < src.length; ++j) {
//依次將src數組中的元素取出
Entry<K,V> e = src[j];
//取出後將src中去除引用
src[j] = null;
//遍歷鏈表
while (e != null) {
//取當前節點的下一節點next
Entry<K,V> next = e.next;
//獲取弱鍵
Object key = e.get();
//弱鍵爲空則說明已被GC回收,將該節點清空,方便GC回收該Entry
if (key == null) {
e.next = null; // Help GC
e.value = null; // " "
size--;
} else {
//不爲空說明沒有被回收,重新計算在dest數組中的索引
int i = indexFor(e.hash, dest.length);
//將該節點插入dest索引i處鏈表頭
e.next = dest[i];
dest[i] = e;
}
e = next;
}
}
}
//將一個map中的所有元素全部放入WeakHashMap
public void putAll(Map<? extends K, ? extends V> m) {
//取傳入map的大小
int numKeysToBeAdded = m.size();
//若傳入的是一個空map,直接return
if (numKeysToBeAdded == 0)
return;
/*
* Expand the map if the map if the number of mappings to be added
* is greater than or equal to threshold. This is conservative; the
* obvious condition is (m.size() + size) >= threshold, but this
* condition could result in a map with twice the appropriate capacity,
* if the keys to be added overlap with the keys already in this map.
* By using the conservative calculation, we subject ourself
* to at most one extra resize.
*/
//如果要添加映射的數量大於或等於閾值,這是保守的;明顯的條件是(m.size() + size) >= 閾值,
//但是這個條件可能導致map的容量是適當容量的兩倍,如果要添加的鍵與此映射中已經存在的鍵重疊。
//通過使用保守的計算,我們最多可額外調整一次大小。
if (numKeysToBeAdded > threshold) {
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
//遍歷map,依次將元素存入WeakHashMap中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
//根據key將Entry移除WeakHashMap
public V remove(Object key) {
//檢查key爲null則返回NULL_KEY對象
Object k = maskNull(key);
//取key的hashCode
int h = hash(k);
//獲取Entry數組,獲取之前先刪除所有需要被移除的Entry
Entry<K,V>[] tab = getTable();
//計算出該key的下標索引
int i = indexFor(h, tab.length);
//取索引i處的Entry,用來遍歷鏈表時存放上個節點
Entry<K,V> prev = tab[i];
//用來遍歷鏈表時存放當前節點
Entry<K,V> e = prev;
//遍歷鏈表
while (e != null) {
//取當前節點的下一節點next
Entry<K,V> next = e.next;
//根據hashCode值與equals比較雙重判斷key是否相同,存在則移除該節點並返回該節點value
if (h == e.hash && eq(k, e.get())) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return e.value;
}
//不存在繼續遍歷
prev = e;
e = next;
}
return null;
}
//移除WeakHashMap中指定Entry
boolean removeMapping(Object o) {
//判斷對象o是否爲Entry實例,不是則直接return
if (!(o instanceof Map.Entry))
return false;
//獲取Entry數組,獲取之前先刪除所有需要被移除的Entry
Entry<K,V>[] tab = getTable();
//將對象o強轉爲Entry實體
Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
//檢查key爲null則返回NULL_KEY對象
Object k = maskNull(entry.getKey());
//取key的hashCode
int h = hash(k);
//計算出該key的下標索引
int i = indexFor(h, tab.length);
//取索引i處的Entry,用來遍歷鏈表時存放上個節點
Entry<K,V> prev = tab[i];
//用來遍歷鏈表時存放當前節點
Entry<K,V> e = prev;
//遍歷鏈表
while (e != null) {
//取當前節點的下一節點next
Entry<K,V> next = e.next;
//根據hashCode值與equals比較雙重判斷Entry是否爲要移除的對象,若是則移除並返回true
if (h == e.hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return true;
}
//若不是則繼續遍歷
prev = e;
e = next;
}
return false;
}
//清空WeakHashMap
public void clear() {
// clear out ref queue. We don't need to expunge entries
// since table is getting cleared.
//清空queue,將queue中的元素一個個取出
while (queue.poll() != null)
;
modCount++;
//通過Arrays.fill把底層數組所有元素全部清空
Arrays.fill(table, null);
size = 0;
// Allocation of array may have caused GC, which may have caused
// additional entries to go stale. Removing these entries from the
// reference queue will make them eligible for reclamation.
//將數組清空後可能引發了GC導致queue中又添加了元素,再次清空
while (queue.poll() != null)
;
}
//判斷WeakHashMap中是否包含value
public boolean containsValue(Object value) {
//如果value爲空,調用是否包含空value邏輯
if (value==null)
return containsNullValue();
//獲取Entry數組,獲取之前先刪除所有需要被移除的Entry
Entry<K,V>[] tab = getTable();
//遍歷
for (int i = tab.length; i-- > 0;)
//遍歷鏈表
for (Entry<K,V> e = tab[i]; e != null; e = e.next)
//equals比較value值,相同則返回true
if (value.equals(e.value))
return true;
return false;
}
//判斷是否包含空value
private boolean containsNullValue() {
Entry<K,V>[] tab = getTable();
for (int i = tab.length; i-- > 0;)
for (Entry<K,V> e = tab[i]; e != null; e = e.next)
if (e.value==null)
return true;
return false;
}
//返回WeakHashMap中的所有key的set集合
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
//返回WeakHashMap中的所有value的集合
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
//返回WeakHashMap中的所有Entry的set集合
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
expungeStaleEntries源碼總結:
①:循環遍歷引用隊列(queue), 如果發現某個對象被GC了,那麼就開始處理。
②:如果被處理的這個節點是頭節點,那麼直接把該節點的下個節點放到頭節點,然後幫助GC去除value的引用,接着把WeakHashMap的大小減1。
③:如果被處理的這個節點不是頭結點,那麼就需要把這個節點的上個節點中的next指針直接指向當前節點的下個節點。意思就是a->b->c,這個時候要移除b,那麼就變成a->c。然後幫助GC去除value的引用,接着把WeakHashMap的大小減1。