Welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.
Introduction to the
WeakHashMap is a weak reference map, internal keys will be stored as weak references, when THE JVM GC, if these keys do not have strong references, will be recycled by GC, the next time when we operate map will delete the corresponding Entry, based on this feature, WeakHashMap is particularly useful for cache processing.
Inheritance system
As can be seen, WeakHashMap does not implement Clone and Serializable interfaces, so it does not have Clone and serialization features.
Storage structure
WeakHashMap will recycle the key without strong reference during GC, so it is doomed that there will not be too many elements in it, so it does not need to be processed like HashMap when there are many elements into a red-black tree.
Therefore, WeakHashMap only has (array + linked list) storage structure.
The source code parsing
attribute
/** * Default initial capacity is 16 */
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/** * The maximum capacity is 2 ^ 30 */
private static final int MAXIMUM_CAPACITY = 1 << 30;
/** * Default load factor */
private static final float DEFAULT_LOAD_FACTOR = 0.75 f;
/ * * * * /
Entry<K,V>[] table;
/** * Number of elements */
private int size;
/** * Capacity expansion threshold, equal to capacity * loadFactor */
private int threshold;
/** * load factor */
private final float loadFactor;
/** * references the queue to which Entry is added when weak keys fail */
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
Copy the code
(1) Capacity
The capacity is the length of the array, that is, the number of buckets. The default value is 16, and the maximum value is 2 to the power of 30. When the capacity reaches 64, it can be treed.
(2) Loading factor
The load factor is used to calculate the capacity to be expanded. The default load factor is 0.75.
(3) Reference queue
When a weak key is invalid, an Entry is added to the queue, and the invalid Entry is deleted when the map is accessed next time.
Entry inner class
WeakHashMap Internal storage node without key attribute.
private static class Entry<K.V> extends WeakReference<Object> implements Map.Entry<K.V> {
// You can see that there is no key because the key is stored as a weak reference in the Referen class
V value;
final int hash;
Entry<K,V> next;
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
// Call WeakReference's constructor to initialize the key and reference queue
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next; }}public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent, ReferenceQueue<? super T> q) {
// Call Reference's constructor to initialize the key and Reference queue
super(referent, q); }}public abstract class Reference<T> {
// Where the key is actually stored
private T referent; /* Treated specially by GC */
// Reference queue
volatile ReferenceQueue<? super T> queue;
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null)? ReferenceQueue.NULL : queue; }}Copy the code
From the Entry constructor, we know that the key and queue will eventually be passed to the Reference constructor, where the key is the referent property of Reference. It will be treated specially by gc, that is, when no strong Reference exists, it will be cleared by the next GC.
A constructor
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(a) {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
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);
}
Copy the code
The construction method is similar to that of a HashMap. The initial capacity is greater than or equal to the nearest incoming capacity, 2 to the n power, and the threshold is equal to capacity * loadFactor.
Put (K key, V value) method
Methods to add elements.
public V put(K key, V value) {
// If key is empty, use an empty object instead
Object k = maskNull(key);
// Computes the hash value of the key
int h = hash(k);
/ / get barrels
Entry<K,V>[] tab = getTable();
// Calculate the element in which bucket, h & (length-1)
int i = indexFor(h, tab.length);
// Iterate through the bucket list
for(Entry<K,V> e = tab[i]; e ! =null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
// If an element is found, it replaces the old value with the new value and returns the old value
V oldValue = e.value;
if(value ! = oldValue) e.value = value;return oldValue;
}
}
modCount++;
// Insert the new value to the head of the list if it is not found
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
// If the number of elements inserted reaches the threshold, expand the number of buckets twice the size
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
Copy the code
(1) Compute hash;
This is different from a HashMap, where zero is returned if the key is empty, which uses an empty object.
The HashMap uses only one xor. Here it uses four. The HashMap gives the interpretation that once is enough, and even if there is a conflict, it will be converted to a red-black tree.
(2) Calculate which bucket it is in;
(3) Traversing the linked list corresponding to buckets;
(4) If the element is found, it replaces the old value with the new value and returns the old value;
(5) Insert a new element into the head of the list if it is not found;
The HashMap is inserted at the end of the list.
(6) If the number of elements reaches the threshold, expand the capacity to twice the size;
In HashMap, capacity expansion is performed when the value is greater than threshold.
The resize (int newCapacity) method
Capacity expansion method.
void resize(int newCapacity) {
// Retrieves old buckets. Invalid entries are excluded from getTable()
Entry<K,V>[] oldTable = getTable();
/ / the old capacity
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
/ / new barrels
Entry<K,V>[] newTable = newTable(newCapacity);
// Move elements from the old bucket to the new bucket
transfer(oldTable, newTable);
// Assign a bucket variable to the new bucket
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. */
// If the number of elements is greater than half of the threshold, use a new bucket and a new capacity, and calculate a new threshold
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
// If not, move the element back to the old bucket
// Since invalid entries will be cleared during transfer, the number of elements may not be so large, so there is no need to expandexpungeStaleEntries(); transfer(newTable, oldTable); table = oldTable; }}private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
// Walk through the old bucket
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 the key is null, the entire Entry is cleared
if (key == null) {
e.next = null; // Help GC
e.value = null; / / ""
size--;
} else {
// Otherwise calculate the position in the new bucket and place the element at the head of the new bucket's corresponding list
inti = indexFor(e.hash, dest.length); e.next = dest[i]; dest[i] = e; } e = next; }}}Copy the code
(1) Judge whether the old capacity has reached the maximum capacity;
(2) Create a new bucket and transfer all elements to the new bucket;
(3) If the number of elements after transfer is less than half of the threshold for capacity expansion, the elements are transferred back to the old bucket and the old bucket continues to be used, indicating that capacity expansion is not required;
(4) Otherwise, use a new bucket and calculate a new capacity expansion threshold;
(5) The element whose key is null will be removed in the process of element transfer, so the size will be smaller;
Get (Object key) method
Gets the element.
public V get(Object key) {
Object k = maskNull(key);
/ / calculate the hash
int h = hash(k);
Entry<K,V>[] tab = getTable();
int index = indexFor(h, tab.length);
Entry<K,V> e = tab[index];
// Run through the list and return if found
while(e ! =null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}
Copy the code
(1) Compute the hash value;
(2) Traverse the linked list corresponding to the bucket;
(3) Return the value of the element if found;
(4) Return null if not found;
Remove (Object key) method
Remove elements.
public V remove(Object key) {
Object k = maskNull(key);
/ / calculate the hash
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
// The first element of the bucket in which the element is located
Entry<K,V> prev = tab[i];
Entry<K,V> e = prev;
// Iterate over the list
while(e ! =null) {
Entry<K,V> next = e.next;
if (h == e.hash && eq(k, e.get())) {
// Delete the element if it is found
modCount++;
size--;
if (prev == e)
// If it is a head node, the head node points to the next node
tab[i] = next;
else
// If it is not the head node, delete the node
prev.next = next;
return e.value;
}
prev = e;
e = next;
}
return null;
}
Copy the code
(1) Compute hash;
(2) Find the bucket;
(3) Traversing the linked list corresponding to buckets;
(4) Delete the node if it is found and return the value of the node;
(5) Return null if not found;
ExpungeStaleEntries () method
Delete invalid entries.
private void expungeStaleEntries(a) {
// Traverse the reference queue
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);
// Find the bucket
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
// Iterate over the list
while(p ! =null) {
Entry<K,V> next = p.next;
// Find the element
if (p == e) {
// Delete the element
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break; } prev = p; p = next; }}}}Copy the code
(1) When the key is invalid, GC will automatically add the corresponding Entry to the reference queue;
(2) All map operations, such as getTable(), size(), and resize(), are directly or indirectly invoked to remove invalid entries.
(3) The purpose of this method is to traverse the reference queue and remove the entries stored in it from the map. See the class comment for details.
(4) As you can see from this, removing Entry and setting value to NULL helps gc clean up elements, defensive programming.
Use case
Having said so much, one can’t get by without giving an example of its use.
package com.coolcoding.code;
import java.util.Map;
import java.util.WeakHashMap;
public class WeakHashMapTest {
public static void main(String[] args) {
Map<String, Integer> map = new WeakHashMap<>(3);
// Put three strings declared by new String()
map.put(new String("1"), 1);
map.put(new String("2"), 2);
map.put(new String("3"), 3);
// Put strings that are not declared by new String()
map.put("6".6);
// use key to strongly reference the string "3"
String key = null;
for (String s : map.keySet()) {
// This "3" is not a reference to the new String("3")
if (s.equals("3")) { key = s; }}{6=6, 1=1, 2=2, 3=3}, all keys can be printed without GC
System.out.println(map);
/ / gc
System.gc();
// Put a String declared by new String()
map.put(new String("4"), 4);
// output {4=4, 6=6, 3=3}, after gc values and strongly referenced keys can be printed out
System.out.println(map);
// Key and "3" references are broken
key = null;
/ / gc
System.gc();
// output {6=6}, strong referenced keys can be printed after gcSystem.out.println(map); }}Copy the code
In this case, variables declared by new String() are weak references. Using the “6” declaration will always be in the constant pool and will not be cleaned up, so the “6” element will always be in the map and all other elements will be cleaned up by gc.
conclusion
(1) WeakHashMap uses (array + linked list) storage structure;
(2) Key in WeakHashMap is a weak reference, which will be cleared during GC;
(3) Entries corresponding to invalid keys are excluded in each map operation.
(4) When you use String as a key, you must use new String() to declare the key. Other basic types are wrapped in the same type.
(5) WeakHashMap is commonly used as cache;
Source address with detailed comments
WeakHashMap.java
eggs
What do you know about strong, Soft, weak, and virtual references?
(1) Strong reference
Use the most common references. If an object has a strong reference, it will never be collected by gc. If memory runs out, the GC would rather throw outofMemoryErrors than reclaim objects with strong references.
(2) Soft reference
If an object has only soft references, it will not be recycled if there is enough memory, but it will be recycled if there is not enough memory. As long as the object with a soft reference is not reclaimed, the program is fine.
(3) Weak reference
If an object has only weak references, it will be reclaimed when the GC scans it, regardless of whether there is enough memory.
(4) Virtual reference
If an object has only virtual references, it can be reclaimed by gc at any time, just as if it had no references at all.
Soft (weak, virtual) references must be used with a ReferenceQueue (ReferenceQueue) into which the gc will put the soft (weak, virtual) reference when it retrieves the object referenced by it.
For example, the Entry above is a weak reference to a key. When the key is reclaimed, the Entry is placed in a queue.
Welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.