目录
一. 前言
二. 源码解析
2.1. 类结构
2.2. 成员变量
2.3. 构造方法
2.4. Entry
2.5. 添加元素
2.6. 扩容
2.7. 删除元素
2.8. 获取元素
一. 前言
WeakHashMap,从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的entry可能会被GC自动删除,即使程序员没有调用remove()或者clear()方法。
更直观的说,当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况:
1. 调用两次size()方法返回不同的值;
2. 两次调用isEmpty()方法,第一次返回false,第二次返回true;
3. 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
4. 两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。
遇到这么奇葩的现象,你是不是觉得使用者一定会疯掉?其实不然,WeakHashMap 的这个特点特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到。
要明白 WeakHashMap 的工作原理,还需要引入一个概念:弱引用(WeakReference)。我们都知道Java中的内存是通过GC自动管理的,GC会在程序运行过程中自动判断哪些对象是可以被回收的,并在合适的时机进行内存释放。GC判断某个对象是否可被回收的依据是,是否有有效的引用指向该对象。如果没有有效引用指向该对象(基本意味着不存在访问该对象的方式),那么该对象就是可回收的。这里的有效引用并不包括弱引用。也就是说,虽然弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收。
WeakHashMap 内部是通过弱引用来管理entry的,弱引用的特性对应到 WeakHashMap 上意味着什么呢?将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收,除非在 WeakHashMap 之外还有对该key的强引用。
关于强引用,弱引用等概念请参见《Java 四种引用类型》。
二. 源码解析
如果你看过前几篇关于 Map 和 Set 的讲解,一定会问:既然有 WeakHashMap,是否有 WeekHashSet 呢?答案是没有。不过Java Collections工具类给出了解决方案,Collections.newSetFromMap(Map<E,Boolean> map) 方法可以将任何 Map包装成一个Set。通过如下方式可以快速得到一个 WeakHashSet:
// 将WeakHashMap包装成一个Set
Set<Object> weakHashSet = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());// Collections.newSetFromMap()用于将任何Map包装成一个Set
public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {return new SetFromMap<>(map);
}private static class SetFromMap<E> extends AbstractSet<E>implements Set<E>, Serializable
{private final Map<E, Boolean> m; // The backing mapprivate transient Set<E> s; // Its keySetSetFromMap(Map<E, Boolean> map) {if (!map.isEmpty())throw new IllegalArgumentException("Map is non-empty");m = map;s = map.keySet();}public void clear() { m.clear(); }public int size() { return m.size(); }public boolean isEmpty() { return m.isEmpty(); }public boolean contains(Object o) { return m.containsKey(o); }public boolean remove(Object o) { return m.remove(o) != null; }public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }public Iterator<E> iterator() { return s.iterator(); }public Object[] toArray() { return s.toArray(); }public <T> T[] toArray(T[] a) { return s.toArray(a); }public String toString() { return s.toString(); }public int hashCode() { return s.hashCode(); }public boolean equals(Object o) { return o == this || s.equals(o); }public boolean containsAll(Collection<?> c) {return s.containsAll(c);}public boolean removeAll(Collection<?> c) {return s.removeAll(c);}public boolean retainAll(Collection<?> c) {return s.retainAll(c);}// addAll is the only inherited implementation// Override default methods in Collection@Overridepublic void forEach(Consumer<? super E> action) {s.forEach(action);}@Overridepublic boolean removeIf(Predicate<? super E> filter) {return s.removeIf(filter);}@Overridepublic Spliterator<E> spliterator() {return s.spliterator();}@Overridepublic Stream<E> stream() {return s.stream();}@Overridepublic Stream<E> parallelStream() {return s.parallelStream();}private static final long serialVersionUID = 2454657854757543876L;private void readObject(java.io.ObjectInputStream stream)throws IOException, ClassNotFoundException{stream.defaultReadObject();s = m.keySet();}
}
2.1. 类结构
从图中可以看出:WeakHashMap继承于AbstractMap,并且实现了Map接口。
2.2. 成员变量
// 默认的初始容量是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的幂。
// WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
private Entry[] table;// WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
private int size;// WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
private int threshold;// 加载因子实际大小
private final float loadFactor;// queue保存的是“已被GC清除”的“弱引用的键”。
// 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();// WeakHashMap被改变的次数
int modCount;
2.3. 构造方法
// 指定“容量大小”和“加载因子”的构造函数
public WeakHashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Initial Capacity: "+initialCapacity);// WeakHashMap的最大容量只能是MAXIMUM_CAPACITYif (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal Load factor: "+loadFactor);// 找出“大于initialCapacity”的最小的2的幂int capacity = 1;while (capacity < initialCapacity)capacity <<= 1;// 创建Entry数组,用来保存数据table = newTable(capacity);// 设置“加载因子”this.loadFactor = loadFactor;// 设置“WeakHashMap阈值”,当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。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);// 将m中的全部元素逐个添加到WeakHashMap中putAll(m);
}private Entry<K,V>[] newTable(int n) {return (Entry<K,V>[]) new Entry<?,?>[n];
}
2.4. Entry
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {V value;final int hash;Entry<K,V> next;/*** Creates new entry.*/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;}@SuppressWarnings("unchecked")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);}public String toString() {return getKey() + "=" + getValue();}
}
可以看到Entry是继承WeakReference的,我们结合WeakReference再看一下:
public class WeakReference<T> extends Reference<T> {/*** Creates a new weak reference that refers to the given object. The new* reference is not registered with any queue.* 创建一个新的弱应用给传入的对象,这个新的引用不注册任何队列** @param referent object the new weak reference will refer to*/public WeakReference(T referent) {super(referent);}/*** Creates a new weak reference that refers to the given object and is* registered with the given queue.* 创建一个新的弱应用给传入的对象,这个新的引用注册给一个给定的队列** @param referent object the new weak reference will refer to* @param q the queue with which the reference is to be registered,* or <tt>null</tt> if registration is not required*/public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}
}
我们发现在WeakHashMap中把key注册给了WeakReference,也就是说在WeakHashMap中key是一个弱引用。
2.5. 添加元素
public V put(K key, V value) {// 如果key是null则给定一个空的对象进行修饰Object k = maskNull(key);// 计算key的hashint h = hash(k);// 获取tableEntry<K,V>[] tab = getTable();// 根据hash找到数组下标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;
}private Entry<K,V>[] getTable() {expungeStaleEntries();return table;
}private void expungeStaleEntries() {// 从 ReferenceQueue中拉取元素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;elseprev.next = next;// Must not null out e.next;// stale entries may be in use by a HashIterator// 拿到entry的值赋值为null帮助GCe.value = null; // Help GCsize--;break;}prev = p;p = next;}}}
}
expungeStaleEntries 就是WeakHashMap的核心了,它承担着Map中死对象的清理工作。原理就是依赖WeakReference和ReferenceQueue的特性。在每个WeakHashMap都有个ReferenceQueue queue,在Entry初始化的时候也会将queue传给WeakReference,这样当某个key可以失去所有强引用之后,其key对应的WeakReference对象会被放到queue里,有了queue就知道需要清理哪些Entry了。
这里也是整个WeakHashMap里唯一加了同步的地方。除了上文说的到resize中调用了expungeStaleEntries(),size()中也调用了这个清理方法。另外 getTable()也调了,这就意味着几乎所有其他方法都间接调用了清理。
// 将"m"的全部元素都添加到WeakHashMap中
public void putAll(Map<? extends K, ? extends V> m) {int numKeysToBeAdded = m.size();if (numKeysToBeAdded == 0)return;// 计算容量是否足够,// 若“当前实际容量 < 需要的容量”,则将容量x2。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);}// 将“m”中的元素逐个添加到WeakHashMap中。for (Map.Entry<? extends K, ? extends V> e : m.entrySet())put(e.getKey(), e.getValue());
}
2.6. 扩容
// 重新调整WeakHashMap的大小,newCapacity是调整后的单位
void resize(int newCapacity) {Entry[K, V] oldTable = getTable();int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}// 新建一个newTable,将“旧的table”的全部元素添加到“新的newTable”中,// 然后,将“新的newTable”赋值给“旧的table”。Entry[K, V] newTable = new Table(newCapacity);transfer(oldTable, newTable);table = newTable;if (size >= threshold / 2) {threshold = (int)(newCapacity * loadFactor);} else {// 删除table中“已被GC回收的key对应的键值对”expungeStaleEntries();transfer(newTable, oldTable);table = oldTable;}
}// 将WeakHashMap中的全部元素都添加到newTable中
private void transfer(Entry[] src, Entry[] 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 GCe.value = null; // " "size--;} else {int i = indexFor(e.hash, dest.length);e.next = dest[i];dest[i] = e;}e = next;}}
}
2.7. 删除元素
// 删除“键为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;// 删除链表中“键为key”的元素// 本质是“删除单向链表中的节点”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;elseprev.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;// 删除链表中的“键值对e”// 本质是“删除单向链表中的节点”while (e != null) {Entry<K,V> next = e.next;if (h == e.hash && e.equals(entry)) {modCount++;size--;if (prev == e)tab[i] = next;elseprev.next = next;return e;}prev = e;e = next;}return false;
}// 清空WeakHashMap,将所有的元素设为null
public void clear() {while (queue.poll() != null);modCount++;Arrays.fill(table, null);size = 0;while (queue.poll() != null);
}
2.8. 获取元素
// 获取key对应的value
public V get(Object key) {Object k = maskNull(key);// 获取key的hash值。int h = hash(k);Entry<K,V>[] tab = getTable();int index = indexFor(h, tab.length);Entry<K,V> e = tab[index];// 在“该hash值对应的链表”上查找“键值等于key”的元素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<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;
}