WeakHashMap 继承于AbstractMap,实现了Map接口。和HashMap一样,WeakHashMap也是一个哈希表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
WeakHashMap中的每个关键对象间接存储为弱引用的引用。因此,只有在地图的内部和外部的弱引用之后,密钥才会被垃圾回收器清除。
一、WeakHashMap的垃圾回收机制
1.1 WeakHashMap的Entry定义
该散列表中的条目扩展了WeakReference,使用其主ref字段作为关键字。
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; } public K getKey() { return (K) WeakHashMap.unmaskNull(get()); } public V getValue() { return value; } public V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; K k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { V v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public int hashCode() { K k = getKey(); V v = getValue(); return Objects.hashCode(k) ^ Objects.hashCode(v);
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
} public String toString() { return getKey() + "=" + getValue(); } } 1.2 回收机制
定义了一个queue,保存的是“已被GC清除”的“弱引用的键”。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
从表中删除陈旧的条目。即删除与引用队列中重合的元素
三、主要方法private void expungeStaleEntries() { 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; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; else prev.next = next; //陈旧的条目可能由HashIterator使用 e.value = null; //帮助垃圾回收 size--; break; } prev = p; p = next; } } } }
//返回指定键映射到的值,如果此映射不包含键的映射,则返回null。 public V get(Object key) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int index = indexFor(h, tab.length); Entry<K,V> e = tab[index]; while (e != null) { //遍历hash表 if (e.hash == h && eq(k, e.get())) //判断hash值和key值 return e.value; e = e.next; } return null; } //如果此映射包含指定键的映射,则返回true。 public boolean containsKey(Object key) { return getEntry(key) != null; } Entry<K,V> getEntry(Object key) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int index = indexFor(h, tab.length); Entry<K,V> e = tab[index]; while (e != null && !(e.hash == h && eq(k, e.get()))) e = e.next; return e; } //将指定的值与此映射中的指定键相关联。 public V put(K key, V value) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int i = indexFor(h, tab.length); //遍历表得到key,用新value替换旧的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; } } modCount++; Entry<K,V> e = tab[i]; //新的Entry添加到哈希表 tab[i] = new Entry<>(k, value, queue, h, e); if (++size >= threshold) resize(tab.length * 2); return null; } //将该map的内容重新设置为具有较大容量的新阵列。 当此map中的键数达到其阈值时,将自动调用此方法。 如果当前容量为MAXIMUM_CAPACITY,则此方法不会调整映射大小,而是将阈值设置为Integer.MAX_VALUE。 这具有防止未来呼叫的效果。 void resize(int newCapacity) { Entry<K,V>[] oldTable = getTable(); int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry<K,V>[] newTable = newTable(newCapacity); transfer(oldTable, newTable); table = newTable; 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) { for (int j = 0; j < src.length; ++j) { Entry<K,V> e = src[j]; src[j] = null; //将src所有的内容设置为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; } } } //将指定map的所有映射复制到此地图。这些映射将替换此映射对当前指定映射中的任何键的任何映射。 public void putAll(Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; 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); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); } //如果存在,则从此弱散列映射中删除key的映射。 public V remove(Object key) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int i = indexFor(h, tab.length); Entry<K,V> prev = tab[i]; Entry<K,V> e = prev; //与删除单向链表中的节点原理相同 while (e != null) { Entry<K,V> next = e.next; 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; } boolean removeMapping(Object o) { if (!(o instanceof Map.Entry)) return false; Entry<K,V>[] tab = getTable(); Map.Entry<?,?> entry = (Map.Entry<?,?>)o; Object k = maskNull(entry.getKey()); int h = hash(k); int i = indexFor(h, tab.length); Entry<K,V> prev = tab[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; 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; } //从此map中删除所有的映射。 public void clear() { while (queue.poll() != null) modCount++; Arrays.fill(table, null); size = 0; while (queue.poll() != null) ; } //如果此映射将一个或多个键映射到指定的值,则返回true。 public boolean containsValue(Object value) { if (value==null) return 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 (value.equals(e.value)) return true; return false; } //是否包含null值 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; }