1、WeakHashMap-源码分析-底层数据结构
1、WeakHashMap-底层数据结构 1-1、WeakHashMap是哈希表,与HashMap区别不大,但是它的键是"弱键"。 1-2、故WeakHashMap底层数据结构=数组+链表(未进行链表和红黑树转换,得与HashMap区分)。2、WeakHashMap-属性字段: // 默认的初始容量是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<K> queue = new ReferenceQueue<K>(); // WeakHashMap被改变的次数 private volatile int modCount; //创建一个大小为n的Entry数组 @SuppressWarnings("unchecked") private Entry<K,V>[] newTable(int n) { return (Entry<K,V>[]) new Entry<?,?>[n]; } /** * 表示表内的空键。 */ private static final Object NULL_KEY = new Object();
2、WeakHashMap-源码分析-构造方法
1、WeakHashMap-源码分析-构造方法: //默认构造函数,默认的初始容量是16,默认加载因子0.75f public WeakHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } //指定"容量大小"的构造函数,自定义初始容量,默认加载因子0.75f public WeakHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } //指定"容量大小"和"加载因子"的构造函数 public WeakHashMap(int initialCapacity, float loadFactor) { //指定的初始容量必须大于0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity); // WeakHashMap的最大容量只能是MAXIMUM_CAPACITY:必须是2的幂且小于2的30次方 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; // 创建Entry数组,用来保存数据 table = newTable(capacity); this.loadFactor = loadFactor; // 设置"WeakHashMap阈值",当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。 threshold = (int)(capacity * loadFactor); } // 包含"子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); putAll(m); }
3、WeakHashMap-源码分析-Entry类
1、WeakHashMap中的核心角色--Entry,Entry继承了WeakReference,实现了Map.Entry类 1-1、Entry继承自WeakReference,也就是弱引用,所以就具有了弱引用的特点。 1-2、实现了Map.Entry类,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()等这些函数。 1-3、其他WeakHashMap中的Entry最大的不同就是继承自WeakReference,并把key保存在了WeakReference中。2、Entry类源代码: //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实例 * queue是用来存放那些,被jvm清除的entry的引用,因为WeakHashMap使用的是弱引用,所以一旦gc,就会有key键被清除,所以会把entry加入到queue中。 * 就是为.expungeStaleEntries()方法所用。 */ Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { //它调用了父类的构造函数,调用的WeakReference的构造函数,将key传入Reference中,保存在referent成员变量中 super(key, queue); this.value = value; this.hash = hash; this.next = next; } @SuppressWarnings("unchecked") public K getKey() { // 这里调用了Reference的get方法,从中取出referent对象 // WeakHashMap中,key如果为null会使用NULL_KEY来替代 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(); //把key和value的hashcode做一个异或处理 return Objects.hashCode(k) ^ Objects.hashCode(v); } public String toString() { return getKey() + "=" + getValue(); } }2、构造方法调用其他方法: //.unmaskNull()方法 //调用这个方法,因为WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量: //private static final Object NULL_KEY = new Object(); static Object unmaskNull(Object key) { return (key == NULL_KEY) ? null : key; } //处理null值, WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量: //private static final Object NULL_KEY = new Object(); private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; }
4、WeakHashMap-源码分析-添加元素
1、添加元素:.put()方法2、源代码: public V put(K key, V value) { //key为null值处理 Object k = maskNull(key); //计算hash值 int h = hash(k); //获取table 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; } //处理null值, WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量: //private static final Object NULL_KEY = new Object(); private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; } //计算hash值 final int hash(Object k) { int h = k.hashCode(); //做了二次散列,来扩大低位的影响。 //h >>> 20 = h*2^20、h >>> 12 = h*2^12 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * 根据hashcode值,计算下标 * 将数组长度减1后与hashcode做一个位与操作,因为length必定是2的幂,所以减1后就变成了掩码,再进行与操作就能直接得到hashcode mod length的结果。 * h & (length-1) = h/length */ private static int indexFor(int h, int length) { return h & (length-1); } //获取table /** * 返回第一次清除过时项后的表。 */ private Entry<K,V>[] getTable() { expungeStaleEntries(); return table; } /** * 从表中删除过时的条目:都会将table中key已经被回收掉的Entry移除掉(将对应的value置为null)。 */ 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 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; // 将对应的value置为null,帮助GC回收 e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
5、WeakHashMap-源码分析-扩容
1、扩容:.resize()方法2、源代码: void resize(int newCapacity) { //获取当前table Entry<K,V>[] oldTable = getTable(); int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //新建一个table Entry<K,V>[] newTable = newTable(newCapacity); transfer(oldTable, newTable); 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 (size >= threshold / 2) { threshold = (int)(newCapacity * loadFactor); } else { //从表中删除过时的条目:都会将table中key已经被回收掉的Entry移除掉。 expungeStaleEntries(); // 将旧table中的内容复制到新table中 transfer(newTable, oldTable); table = oldTable; } } // 新建Entry数组 private Entry<K,V>[] newTable(int n) { return (Entry<K,V>[]) new Entry<?,?>[n]; } /** Transfers all entries from src to dest tables 将旧table中的内容复制到新table中 */ 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; } } }
6、WeakHashMap-源码分析-获取元素
1、获取元素:.get()方法2、源代码: public V get(Object key) { // 对null值特殊处理 Object k = maskNull(key); // 取key的hash值 int h = hash(k); // 取当前table Entry<K,V>[] tab = getTable(); // 获取下标 int index = indexFor(h, tab.length); Entry<K,V> e = tab[index]; // 链表中查找元素 while (e != null) { if (e.hash == h && eq(k, e.get())) return e.value; e = e.next; } return null; }
7、WeakHashMap-源码分析-删除元素
1、删除元素:.remove()方法2、源代码: public V remove(Object key) { // 对null值特殊处理 Object k = maskNull(key); // 取key的hash int h = hash(k); // 取当前table 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; // 查找对应Entry if (h == e.hash && eq(k, e.get())) { modCount++; size--; if (prev == e) tab[i] = next; else prev.next = next; // 如果找到,返回对应Entry的value return e.value; } prev = e; e = next; } return null; }
8、WeakHashMap-源码分析-举例使用-Demo
public class WeakHashMapTest { public static void main(String[] args){ testWeakHashMap(); } private static void testWeakHashMap() { // 创建3个String对象用来做key String w1 = new String("key1"); String w2 = new String("key2"); String w3 = new String("key3"); // 新建WeakHashMap Map weakHashMap = new WeakHashMap(); // 添加键值对 weakHashMap.put(w1, "v1"); weakHashMap.put(w2, "v2"); weakHashMap.put(w3, "v3"); // 打印出weakHashMap System.out.printf("weakHashMap:%s\n", weakHashMap); //{key1=w1, key2=w2, key3=w3} // containsKey(Object key) :是否包含键key System.out.printf("contains key key1 : %s\n",weakHashMap.containsKey("key1"));//true System.out.printf("contains key key4 : %s\n",weakHashMap.containsKey("key4"));//false // containsValue(Object value) :是否包含值value System.out.printf("contains value v1 : %s\n",weakHashMap.containsValue("v1"));//true System.out.printf("contains value 0 : %s\n",weakHashMap.containsValue(0));//false // remove(Object key) : 删除键key对应的键值对 weakHashMap.remove("three"); System.out.printf("weakHashMap: %s\n", weakHashMap);{key1=w1, key2=w2, key3=w3} // ---- 测试 WeakHashMap 的自动回收特性 ---- // 将w1设置null。 // 这意味着“弱键”w1再没有被其它对象引用,调用gc时会回收WeakHashMap中与“w1”对应的键值对 w1 = null; // 内存回收。这里,会回收WeakHashMap中与“w1”对应的键值对 System.gc(); // 遍历WeakHashMap Iterator iter = weakHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry en = (Map.Entry)iter.next(); /** next : key2 - w2 next : key3 - w3 */ System.out.printf("next : %s - %s\n",en.getKey(),en.getValue()); } // 打印WeakHashMap的实际大小 // after gc WeakHashMap size:2 System.out.printf("after gc WeakHashMap size:%s\n", weakHashMap.size()); }}
9、WeakHashMap-源码分析-应用场景-Demo
1、应用场景 1-1、由于WeakHashMap可以自动清除Entry,所以比较适合用于存储非必需对象,用作缓存非常合适。 1-2、配合事务进行使用,存储事务过程中的各类信息,结构: WeakHashMap<String,Map<K,V>> transactionCache; 1-2-1、key为String类型,可以用来标志区分不同的事务,起到一个事务id的作用。value是一个map,可以是一个简单的HashMap或者LinkedHashMap,用来存放在事务中需要使用到的信息。 1-2-2、在事务开始时创建一个事务id,并用它来作为key,事务结束后,将这个强引用消除掉,这样既能保证在事务中可以获取到所需要的信息,又能自动释放掉map中的所有信息。2、应用Demo-在Tomcat的工具类里,有这样一种实现。基于LRU策略。 package org.apache.tomcat.util.collections; public final class ConcurrentCache<K,V> { private final int size; private final Map<K,V> eden; private final Map<K,V> longterm; public ConcurrentCache(int size) { this.size = size; this.eden = new ConcurrentHashMap<>(size); this.longterm = new WeakHashMap<>(size); } public V get(K k) { V v = this.eden.get(k); if (v == null) { synchronized (longterm) { v = this.longterm.get(k); } if (v != null) { this.eden.put(k, v); } } return v; } public void put(K k, V v) { if (this.eden.size() >= size) { synchronized (longterm) { this.longterm.putAll(this.eden); } this.eden.clear(); } this.eden.put(k, v); } }