微信公衆號: 房東的小黑黑
路途隨遙遠,未來更美好
學海無涯,你們一塊兒加油!web
WeakHashMap與HashMap有些相似,但也有不少地方不一樣。它們設置了相同的負載因子和初始容量,可是前者的數據結構只使用了數組+鏈表
,並無用到紅黑樹,數組
在這裏,與HashMap重複且設置值一致的變量就不重複介紹了,只簡單說下不一樣的地方。微信
表明空Key數據結構
private static final Object NULL_KEY = new Object();
複製代碼
保存GC後被清除的WeakEntriesapp
private final ReferenceQueue\<Object\> queue = new ReferenceQueue\<\>();
複製代碼
Entry: 數組存放節點編輯器
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value; final int hash; Entry<K,V> next; 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
,在其構造函數中,會建立一個新的弱引用指向給定的key。函數
WeakHashMap構造函數this
public WeakHashMap(int initialCapacity, float loadFactor) {
// 部分代碼刪除 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; table = newTable(capacity); this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); } 複製代碼
在構造函數中,它會將數組容量大小設置爲輸入值的最接近的2的n次方;並調用newTable
初始化數組。spa
接下來來分析幾個重要的方法。code
public V put(K key, V value) {
// 當Key爲null時會返回一個名字爲NULL_KEY的Object對象,代表Key支持Null Object k = maskNull(key); int h = hash(k); // 將過時的Entry刪除掉 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; } 複製代碼
在調用get()
、replaceAll()
、containsNullValue()
、forEach()
、removeMapping()
、remove()
、resize()
、put()
等方式時再獲取table數組時,不是直接返回table數組,而是經過getTable方法先把數組中key爲null的Entry刪除掉,再返回。
private Entry<K,V>[] getTable() {
expungeStaleEntries(); return table; } 複製代碼
private void expungeStaleEntries() {
// 從ReferenceQueue中取出過時的節點 for (Object x; (x = queue.poll()) != null; ) { // 鎖住ReferenceQueue synchronized (queue) { @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) x; // 爲了方面,插入indexFor的代碼 /** * private static int indexFor(int h, int length) { return h & (length-1); } **/ int i = indexFor(e.hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { // 若是數組頭節點過時了 if (prev == e) table[i] = next; // 將前驅節點的next指向它的下一個節點,即把該節點從鏈表中去除 else prev.next = next; // 將該節點的value設置爲null,幫助gc e.value = null; // Help GC size--; break; } prev = p; p = next; } } } } 複製代碼
在擴容前,先刪除過時的Entry,而後新建一個容量是原來2倍的數組;以後調用transfer方法進行擴容。若是擴容後size大小大於等於閾值的一半,則更新閾值;若是小於閾值的一半,則再調用一次expungeStaleEntries
方法,再重新錶轉化到原來的舊錶中。
void resize(int newCapacity) {
// 在擴容前先刪除過時的Entry Entry<K,V>[] oldTable = getTable(); int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 擴容爲原來的2倍 Entry<K,V>[] newTable = newTable(newCapacity); transfer(oldTable, newTable); table = newTable; // 若是忽略null元素並處理ref隊列致使大量收縮,則還原舊錶 // 這應該不多見,可是能夠避免垃圾填表的無限擴展。 if (size >= threshold / 2) { threshold = (int)(newCapacity * loadFactor); } else { expungeStaleEntries(); transfer(newTable, oldTable); table = oldTable; } } 複製代碼
遍歷舊數組,若是key爲null,則設置其Entry的next和value都爲null,幫助gc;若是不爲null,則計算該Entry在新數組中的位置,利用頭插法進行插入。
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
for (int j = 0; j < src.length; ++j) { Entry<K,V> e = src[j]; src[j] = null; while (e != null) { Entry<K,V> next = e.next; Object key = e.get(); if (key == null) { e.next = null; // Help GC e.value = null; // " " size--; } else { int i = indexFor(e.hash, dest.length); e.next = dest[i]; dest[i] = e; } e = next; } } } 複製代碼
本文使用 mdnice 排版