1. WeakHashMap简介
WeakHashMap继承自AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap也是一种以key-value键值对的形式进行数据的存储,底层存储结构为数组加链表,并且键值都可以为null。与HashMap不同的是,WeakHashMap实现了弱引用,当一个key不再正常使用时,会从WeakHashMap中自动移除,被GC回收并存入ReferenceQueue中。而这一特点主要是通过ReferenceQueue加WeakReference组合来实现的,ReFerenceQueue主要是配合WeakReference来记录被回收的元素,在WeakReference构造时关联一个ReferenceQueue,当该WeakReference被GC回收后便会将该对象放入ReferenceQueue中,也就是说ReferenceQueue中的key一定是被GC回收过的。所以WeakHashMap在使用中会出现size不断变小,isEmpty会从false变为true等情况。
一般情况下,除非整个map对象被GC回收,否则里面的元素便会一致存在,不会被GC回收。而WeakHashMap中的Entry实现了WeakReference,是一种弱引用类型,弱引用类型的对象会在GC运行时被回收的,也就是说WeakHashMap的Entry中的key只要没有被其他强引用指向,便会在GC运行时被回收。key在被GC回收后会把Entry存入queue,所以这时候仅仅是key被回收了变为null,但是Entry实体还是存在WeakHashMap中的,所以在每次对WeakHashMap进行操作时,都会主动把queue中的所有Entry移除WeakHashMap,利用弱键的机制从而实现了Entry的弱引用。
还有就是WeakHashMap中没有红黑树的优化处理,取而代之的是在hash函数里做了处理,保证了再默认加载因子0.75的情况下,链表中元素数量不会超过8个左右。
使用场景:缓存
2. WeakHashMap实现
1. 核心参数
和HashMap差不多,底层都是通过数组加链表的方式存储数据,默认的初始容量、加载因子和最大容量等参数都和HashMap一致,只不过增加了ReferenceQueue来存储被GC回收的弱键以及NULL_KEY,此外WeakHashMap内部Entry继承自WeakReference,实现了弱引用。在Entry被创建时,会调用父类WeakReference的构造函数并将ReferenceQueue(queue)与其相关联,当该Entry实体的key被GC回收后,便会将该实体放到queue中。
//默认初始容量16 private static final int DEFAULT_INITIAL_CAPACITY = 16; //最大容量2^30 private static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子0.75 private static final float DEFAULT_LOAD_FACTOR = 0.75f; //底层entry数组存储 Entry<K,V>[] table; //WeakHashMap大小 private int size; //扩容阈值 private int threshold; //加载因子 private final float loadFactor; //ReferenceQueue用来存储已被GC回收的弱键 private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); //修改次数,用来实现fail-fast机制 int modCount; //当key为null时,用NULL_KEY来代替 private static final Object NULL_KEY = new Object(); //entry实体的set集合,用于foreach遍历 private transient Set<Map.Entry<K,V>> entrySet; //entry实体,继承自WeakReference,实现了Map.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) {
//调用父类WeakReference的构造函数,把queue作为被GC回收的队列,key没有被引用时会被GC回收,然后将Entry放入queue中 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(); } }
2. 构造函数
WeakHashMap的构造函数和HashMap的构造函数也有点类似,围绕着初始容量和加载因子两个核心参数进行构造。不同的是,WeakHashMap在构造时就已经把底层entry数组实例化了,而HashMap在第一次添加元素时才进行底层Entry数组的实例化。
//构造一个指定加载因子的WeakHashMap,把初始容量设为大于等于initialCapacity的最小2次幂 public WeakHashMap(int initialCapacity, float loadFactor) { //初始容量不能小于0,否则抛出异常 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity); //初始容量不能大于2^30 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //加载因子必须大于0,小于等于1 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; //扩容阈值设为 容量*加载因子 threshold = (int)(capacity * loadFactor); } //构造一个默认加载因子0.75的WeakHashMap,把初始容量设为大于等于initialCapacity的最小2次幂 public WeakHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } //构造一个默认初始容量16、默认加载因子0.75的WeakHashMap public WeakHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } //构造一个默认加载因子0.75的WeakHashMap,初始容量取能够容纳m中元素的最小2次幂(最小为16),并将m中所有元素放入WeakHashMap 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); }
3. 核心方法
//当key为null时,返回一个NULL_KEY对象 private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; } //当对象时NULL_KEY时,返回null static Object unmaskNull(Object key) { return (key == NULL_KEY) ? null : key; } //equals比较 private static boolean eq(Object x, Object y) { return x == y || x.equals(y); } //hash函数 final int hash(Object k) { int h = k.hashCode(); // 这个函数确保在每个位位置上只相差常数倍的哈希码具有有限的冲突数(在默认负载因子下约为8) h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } //根据hashCode和length-1进行与运算得到下标索引 private static int indexFor(int h, int length) { return h & (length-1); } //删除所有被GC回收的Entry private void expungeStaleEntries() { //将queue中的对象依次取出 for (Object x; (x = queue.poll()) != null; ) { //锁住queue,保证对queue的操作是线程安全的 synchronized (queue) { @SuppressWarnings("unchecked") //将取出的对象转为Entry Entry<K,V> e = (Entry<K,V>) x; //获取该Entry的索引i int i = indexFor(e.hash, table.length); //取索引i处的Entry prev,用于遍历链表时存放上一节点 Entry<K,V> prev = table[i]; //声明Entry p,用于遍历链表时存放当前节点 Entry<K,V> p = prev; //遍历链表 while (p != null) { //取当前节点的下一节点next Entry<K,V> next = p.next; //如果当前节点就是被要移除的节点,则进行移除操作 if (p == e) { //如果上个节点就是要被移除的节点,说明被移除的是链表头节点,则把下个节点设为链表头 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; } } } } //获取Entry数组,获取之前先删除所有需要被移除的Entry private Entry<K,V>[] getTable() { expungeStaleEntries(); return table; } //返回WeakHashMap的大小,获取之前先删除所有需要被移除的Entry public int size() { if (size == 0) return 0; expungeStaleEntries(); return size; } //判断WeakHashMap是否为空,获取之前先删除所有需要被移除的Entry public boolean isEmpty() { return size() == 0; } //根据key获取value public V get(Object key) { //检查key为null则返回NULL_KEY对象 Object k = maskNull(key); //取key的hashCode int h = hash(k); //通过getTable获取数组,去除所有需要被回收的Entry Entry<K,V>[] tab = getTable(); //计算出该key的下标索引 int index = indexFor(h, tab.length); //获取该索引位置的链表头 Entry<K,V> e = tab[index]; //遍历链表,根据hashCode值与equals比较双重判断获取value 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实体 Entry<K,V> getEntry(Object key) { //检查key为null则返回NULL_KEY对象 Object k = maskNull(key); //取key的hashCode int h = hash(k); //通过getTable获取数组,去除所有需要被回收的Entry Entry<K,V>[] tab = getTable(); //计算出该key的下标索引 int index = indexFor(h, tab.length); //获取该索引位置的链表头 Entry<K,V> e = tab[index]; //遍历链表,根据hashCode值与equals比较双重判断获取Entry while (e != null && !(e.hash == h && eq(k, e.get()))) e = e.next; return e; } //将key-value存入WeakHashMap,如果key已存在则将value替换并返回旧 public V put(K key, V value) { //检查key为null则返回NULL_KEY对象 Object k = maskNull(key); //取key的hashCode int h = hash(k); //通过getTable获取数组,去除所有需要被回收的Entry Entry<K,V>[] tab = getTable(); //计算出该key的下标索引 int i = indexFor(h, tab.length); //遍历链表,根据hashCode值与equals比较双重判断key是否存在,存在则将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; } } //若key不存在,则将该节点插入链表头,这里和HashMap不太一样,HashMap是尾部追加 modCount++; Entry<K,V> e = tab[i]; tab[i] = new Entry<>(k, value, queue, h, e); //添加元素后的WeakHashMap大小如果大于等于扩容阈值,则扩容1倍 if (++size >= threshold) resize(tab.length * 2); return null; } //核心扩容算法 void resize(int newCapacity) { //获取原Entry数组,获取之前先删除所有需要被移除的Entry Entry<K,V>[] oldTable = getTable(); //获取原Entry数组的容量 int oldCapacity = oldTable.length; //如果原数组的容量已经达到最大值2^30,则停止扩容并防止再次扩容 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //实例化新Entry数组 Entry<K,V>[] newTable = newTable(newCapacity); //将原数组中的元素全部放入新数组 transfer(oldTable, newTable); //将底层数组替换为新数组 table = newTable; //如果忽略空元素并处理ref队列导致大量收缩,则恢复旧表。这应该是很少见的,但是避免了垃圾填充表的无限制扩展。 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) { //遍历src数组 for (int j = 0; j < src.length; ++j) { //依次将src数组中的元素取出 Entry<K,V> e = src[j]; //取出后将src中去除引用 src[j] = null; //遍历链表 while (e != null) { //取当前节点的下一节点next Entry<K,V> next = e.next; //获取弱键 Object key = e.get(); //弱键为空则说明已被GC回收,将该节点清空,方便GC回收该Entry if (key == null) { e.next = null; // Help GC e.value = null; // " " size--; } else { //不为空说明没有被回收,重新计算在dest数组中的索引 int i = indexFor(e.hash, dest.length); //将该节点插入dest索引i处链表头 e.next = dest[i]; dest[i] = e; } e = next; } } } //将一个map中的所有元素全部放入WeakHashMap public void putAll(Map<? extends K, ? extends V> m) { //取传入map的大小 int numKeysToBeAdded = m.size(); //若传入的是一个空map,直接return if (numKeysToBeAdded == 0) return; //如果要添加映射的数量大于或等于阈值,这是保守的;明显的条件是(m.size() + size) >= 阈值, //但是这个条件可能导致map的容量是适当容量的两倍,如果要添加的键与此映射中已经存在的键重叠。 //通过使用保守的计算,我们最多可额外调整一次大小。 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); } //遍历map,依次将元素存入WeakHashMap中 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); } //根据key将Entry移除WeakHashMap public V remove(Object key) { //检查key为null则返回NULL_KEY对象 Object k = maskNull(key); //取key的hashCode int h = hash(k); //获取Entry数组,获取之前先删除所有需要被移除的Entry Entry<K,V>[] tab = getTable(); //计算出该key的下标索引 int i = indexFor(h, tab.length); //取索引i处的Entry,用来遍历链表时存放上个节点 Entry<K,V> prev = tab[i]; //用来遍历链表时存放当前节点 Entry<K,V> e = prev; //遍历链表 while (e != null) { //取当前节点的下一节点next Entry<K,V> next = e.next; //根据hashCode值与equals比较双重判断key是否相同,存在则移除该节点并返回该节点value 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; } //移除WeakHashMap中指定Entry boolean removeMapping(Object o) { //判断对象o是否为Entry实例,不是则直接return if (!(o instanceof Map.Entry)) return false; //获取Entry数组,获取之前先删除所有需要被移除的Entry Entry<K,V>[] tab = getTable(); //将对象o强转为Entry实体 Map.Entry<?,?> entry = (Map.Entry<?,?>)o; //检查key为null则返回NULL_KEY对象 Object k = maskNull(entry.getKey()); //取key的hashCode int h = hash(k); //计算出该key的下标索引 int i = indexFor(h, tab.length); //取索引i处的Entry,用来遍历链表时存放上个节点 Entry<K,V> prev = tab[i]; //用来遍历链表时存放当前节点 Entry<K,V> e = prev; //遍历链表 while (e != null) { //取当前节点的下一节点next Entry<K,V> next = e.next; //根据hashCode值与equals比较双重判断Entry是否为要移除的对象,若是则移除并返回true 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; } //清空WeakHashMap public void clear() { //清空queue,将queue中的元素一个个取出。这是神马骚操作,长见识了!!! while (queue.poll() != null) ; modCount++; //通过Arrays.fill把底层数组所有元素全部清空 Arrays.fill(table, null); size = 0; //将数组清空后可能引发了GC导致queue中又添加了元素,再次清空 while (queue.poll() != null) ; } //判断WeakHashMap中是否包含value public boolean containsValue(Object value) { //如果value为空,调用是否包含空value逻辑 if (value==null) return containsNullValue(); //获取Entry数组,获取之前先删除所有需要被移除的Entry Entry<K,V>[] tab = getTable(); //遍历数组 for (int i = tab.length; i-- > 0;) //遍历链表 for (Entry<K,V> e = tab[i]; e != null; e = e.next) //equals比较value值,相同则返回true if (value.equals(e.value)) return true; return false; } //判断WeakHashMap是否包含空value 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; } //返回WeakHashMap中的所有key的set集合 public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } //返回WeakHashMap中的所有value的集合 public Collection<V> values() { Collection<V> vs = values; if (vs == null) { vs = new Values(); values = vs; } return vs; } //返回WeakHashMap中的所有Entry的set集合 public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); }