WeakHashMap源碼分析

微信公衆號: 房東的小黑黑
路途隨遙遠,未來更美好
學海無涯,你們一塊兒加油!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

Put()方法

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;  } 複製代碼

expungeStaleEntries()方法

在調用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;  }  }  }  } 複製代碼

Resize()擴容

在擴容前,先刪除過時的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;  }  } 複製代碼

transfer()方法

遍歷舊數組,若是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 排版

相關文章
相關標籤/搜索