1、WeakHashMap-源码分析-底层数据结构

  1. 1WeakHashMap-底层数据结构
  2. 1-1WeakHashMap是哈希表,与HashMap区别不大,但是它的键是"弱键"
  3. 1-2、故WeakHashMap底层数据结构=数组+链表(未进行链表和红黑树转换,得与HashMap区分)。
  4. 2WeakHashMap-属性字段:
  5. // 默认的初始容量是16,必须是2的幂。
  6. private static final int DEFAULT_INITIAL_CAPACITY = 16;
  7. // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
  8. private static final int MAXIMUM_CAPACITY = 1 << 30;
  9. // 默认加载因子
  10. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  11. // 存储数据的Entry数组,长度是2的幂。
  12. // WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
  13. private Entry[] table;
  14. // WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
  15. private int size;
  16. // WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
  17. private int threshold;
  18. // 加载因子实际大小
  19. private final float loadFactor;
  20. // queue保存的是“已被GC清除”的“弱引用的键”。
  21. // 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
  22. private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
  23. // WeakHashMap被改变的次数
  24. private volatile int modCount;
  25. //创建一个大小为n的Entry数组
  26. @SuppressWarnings("unchecked")
  27. private Entry<K,V>[] newTable(int n) {
  28. return (Entry<K,V>[]) new Entry<?,?>[n];
  29. }
  30. /**
  31. * 表示表内的空键。
  32. */
  33. private static final Object NULL_KEY = new Object();

2、WeakHashMap-源码分析-构造方法

  1. 1WeakHashMap-源码分析-构造方法:
  2. //默认构造函数,默认的初始容量是16,默认加载因子0.75f
  3. public WeakHashMap() {
  4. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  5. }
  6. //指定"容量大小"的构造函数,自定义初始容量,默认加载因子0.75f
  7. public WeakHashMap(int initialCapacity) {
  8. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  9. }
  10. //指定"容量大小"和"加载因子"的构造函数
  11. public WeakHashMap(int initialCapacity, float loadFactor) {
  12. //指定的初始容量必须大于0
  13. if (initialCapacity < 0)
  14. throw new IllegalArgumentException("Illegal Initial Capacity: "+
  15. initialCapacity);
  16. // WeakHashMap的最大容量只能是MAXIMUM_CAPACITY:必须是2的幂且小于2的30次方
  17. if (initialCapacity > MAXIMUM_CAPACITY)
  18. initialCapacity = MAXIMUM_CAPACITY;
  19. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  20. throw new IllegalArgumentException("Illegal Load factor: "+
  21. loadFactor);
  22. int capacity = 1;
  23. while (capacity < initialCapacity)
  24. capacity <<= 1;
  25. // 创建Entry数组,用来保存数据
  26. table = newTable(capacity);
  27. this.loadFactor = loadFactor;
  28. // 设置"WeakHashMap阈值",当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。
  29. threshold = (int)(capacity * loadFactor);
  30. }
  31. // 包含"子Map"的构造函数
  32. public WeakHashMap(Map<? extends K, ? extends V> m) {
  33. this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
  34. DEFAULT_INITIAL_CAPACITY),
  35. DEFAULT_LOAD_FACTOR);
  36. putAll(m);
  37. }

3、WeakHashMap-源码分析-Entry类

  1. 1WeakHashMap中的核心角色--EntryEntry继承了WeakReference,实现了Map.Entry
  2. 1-1Entry继承自WeakReference,也就是弱引用,所以就具有了弱引用的特点。
  3. 1-2、实现了Map.Entry类,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()等这些函数。
  4. 1-3、其他WeakHashMap中的Entry最大的不同就是继承自WeakReference,并把key保存在了WeakReference中。
  5. 2Entry类源代码:
  6. //Entry是单向链表。
  7. private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
  8. V value;
  9. final int hash;
  10. Entry<K,V> next; // 指向下一个节点
  11. /**
  12. * 构造函数,创建一个新Entry实例
  13. * queue是用来存放那些,被jvm清除的entry的引用,因为WeakHashMap使用的是弱引用,所以一旦gc,就会有key键被清除,所以会把entry加入到queue中。
  14. * 就是为.expungeStaleEntries()方法所用。
  15. */
  16. Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
  17. //它调用了父类的构造函数,调用的WeakReference的构造函数,将key传入Reference中,保存在referent成员变量中
  18. super(key, queue);
  19. this.value = value;
  20. this.hash = hash;
  21. this.next = next;
  22. }
  23. @SuppressWarnings("unchecked")
  24. public K getKey() {
  25. // 这里调用了Reference的get方法,从中取出referent对象
  26. // WeakHashMap中,key如果为null会使用NULL_KEY来替代
  27. return (K) WeakHashMap.unmaskNull(get());
  28. }
  29. public V getValue() {
  30. return value;
  31. }
  32. public V setValue(V newValue) {
  33. V oldValue = value;
  34. value = newValue;
  35. return oldValue;
  36. }
  37. public boolean equals(Object o) {
  38. if (!(o instanceof Map.Entry))
  39. return false;
  40. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  41. K k1 = getKey();
  42. Object k2 = e.getKey();
  43. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  44. V v1 = getValue();
  45. Object v2 = e.getValue();
  46. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  47. return true;
  48. }
  49. return false;
  50. }
  51. public int hashCode() {
  52. K k = getKey();
  53. V v = getValue();
  54. //把key和value的hashcode做一个异或处理
  55. return Objects.hashCode(k) ^ Objects.hashCode(v);
  56. }
  57. public String toString() {
  58. return getKey() + "=" + getValue();
  59. }
  60. }
  61. 2、构造方法调用其他方法:
  62. //.unmaskNull()方法
  63. //调用这个方法,因为WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量:
  64. //private static final Object NULL_KEY = new Object();
  65. static Object unmaskNull(Object key) {
  66. return (key == NULL_KEY) ? null : key;
  67. }
  68. //处理null值, WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量:
  69. //private static final Object NULL_KEY = new Object();
  70. private static Object maskNull(Object key) {
  71. return (key == null) ? NULL_KEY : key;
  72. }

4、WeakHashMap-源码分析-添加元素

  1. 1、添加元素:.put()方法
  2. 2、源代码:
  3. public V put(K key, V value) {
  4. //key为null值处理
  5. Object k = maskNull(key);
  6. //计算hash值
  7. int h = hash(k);
  8. //获取table
  9. Entry<K,V>[] tab = getTable();
  10. //计算下标
  11. int i = indexFor(h, tab.length);
  12. for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
  13. if (h == e.hash && eq(k, e.get())) {
  14. V oldValue = e.value;
  15. if (value != oldValue)
  16. e.value = value;
  17. return oldValue;
  18. }
  19. }
  20. modCount++;
  21. Entry<K,V> e = tab[i];
  22. tab[i] = new Entry<>(k, value, queue, h, e);
  23. //如果元素个数超过阈值,则进行扩容
  24. if (++size >= threshold)
  25. //进行扩容
  26. resize(tab.length * 2);
  27. return null;
  28. }
  29. //处理null值, WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量:
  30. //private static final Object NULL_KEY = new Object();
  31. private static Object maskNull(Object key) {
  32. return (key == null) ? NULL_KEY : key;
  33. }
  34. //计算hash值
  35. final int hash(Object k) {
  36. int h = k.hashCode();
  37. //做了二次散列,来扩大低位的影响。
  38. //h >>> 20 = h*2^20、h >>> 12 = h*2^12
  39. h ^= (h >>> 20) ^ (h >>> 12);
  40. return h ^ (h >>> 7) ^ (h >>> 4);
  41. }
  42. /**
  43. * 根据hashcode值,计算下标
  44. * 将数组长度减1后与hashcode做一个位与操作,因为length必定是2的幂,所以减1后就变成了掩码,再进行与操作就能直接得到hashcode mod length的结果。
  45. * h & (length-1) = h/length
  46. */
  47. private static int indexFor(int h, int length) {
  48. return h & (length-1);
  49. }
  50. //获取table
  51. /**
  52. * 返回第一次清除过时项后的表。
  53. */
  54. private Entry<K,V>[] getTable() {
  55. expungeStaleEntries();
  56. return table;
  57. }
  58. /**
  59. * 从表中删除过时的条目:都会将table中key已经被回收掉的Entry移除掉(将对应的value置为null)。
  60. */
  61. private void expungeStaleEntries() {
  62. for (Object x; (x = queue.poll()) != null; ) {
  63. synchronized (queue) {
  64. @SuppressWarnings("unchecked")
  65. Entry<K,V> e = (Entry<K,V>) x;
  66. // 查找对应的位置
  67. int i = indexFor(e.hash, table.length);
  68. // 找到之前的Entry
  69. Entry<K,V> prev = table[i];
  70. Entry<K,V> p = prev;
  71. // 在链表中寻找
  72. while (p != null) {
  73. Entry<K,V> next = p.next;
  74. if (p == e) {
  75. if (prev == e)
  76. table[i] = next;
  77. else
  78. prev.next = next;
  79. // 将对应的value置为null,帮助GC回收
  80. e.value = null; // Help GC
  81. size--;
  82. break;
  83. }
  84. prev = p;
  85. p = next;
  86. }
  87. }
  88. }
  89. }

5、WeakHashMap-源码分析-扩容

  1. 1、扩容:.resize()方法
  2. 2、源代码:
  3. void resize(int newCapacity) {
  4. //获取当前table
  5. Entry<K,V>[] oldTable = getTable();
  6. int oldCapacity = oldTable.length;
  7. if (oldCapacity == MAXIMUM_CAPACITY) {
  8. threshold = Integer.MAX_VALUE;
  9. return;
  10. }
  11. //新建一个table
  12. Entry<K,V>[] newTable = newTable(newCapacity);
  13. transfer(oldTable, newTable);
  14. table = newTable;
  15. /*
  16. * If ignoring null elements and processing ref queue caused massive
  17. * shrinkage, then restore old table. This should be rare, but avoids
  18. * unbounded expansion of garbage-filled tables.
  19. */
  20. if (size >= threshold / 2) {
  21. threshold = (int)(newCapacity * loadFactor);
  22. } else {
  23. //从表中删除过时的条目:都会将table中key已经被回收掉的Entry移除掉。
  24. expungeStaleEntries();
  25. // 将旧table中的内容复制到新table中
  26. transfer(newTable, oldTable);
  27. table = oldTable;
  28. }
  29. }
  30. // 新建Entry数组
  31. private Entry<K,V>[] newTable(int n) {
  32. return (Entry<K,V>[]) new Entry<?,?>[n];
  33. }
  34. /**
  35. Transfers all entries from src to dest tables
  36. 将旧table中的内容复制到新table中
  37. */
  38. private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
  39. for (int j = 0; j < src.length; ++j) {
  40. Entry<K,V> e = src[j];
  41. src[j] = null;
  42. while (e != null) {
  43. Entry<K,V> next = e.next;
  44. Object key = e.get();
  45. if (key == null) {
  46. e.next = null; // Help GC
  47. e.value = null; // " "
  48. size--;
  49. } else {
  50. int i = indexFor(e.hash, dest.length);
  51. e.next = dest[i];
  52. dest[i] = e;
  53. }
  54. e = next;
  55. }
  56. }
  57. }

6、WeakHashMap-源码分析-获取元素

  1. 1、获取元素:.get()方法
  2. 2、源代码:
  3. public V get(Object key) {
  4. // 对null值特殊处理
  5. Object k = maskNull(key);
  6. // 取key的hash值
  7. int h = hash(k);
  8. // 取当前table
  9. Entry<K,V>[] tab = getTable();
  10. // 获取下标
  11. int index = indexFor(h, tab.length);
  12. Entry<K,V> e = tab[index];
  13. // 链表中查找元素
  14. while (e != null) {
  15. if (e.hash == h && eq(k, e.get()))
  16. return e.value;
  17. e = e.next;
  18. }
  19. return null;
  20. }

7、WeakHashMap-源码分析-删除元素

  1. 1、删除元素:.remove()方法
  2. 2、源代码:
  3. public V remove(Object key) {
  4. // 对null值特殊处理
  5. Object k = maskNull(key);
  6. // 取key的hash
  7. int h = hash(k);
  8. // 取当前table
  9. Entry<K,V>[] tab = getTable();
  10. // 计算下标
  11. int i = indexFor(h, tab.length);
  12. Entry<K,V> prev = tab[i];
  13. Entry<K,V> e = prev;
  14. while (e != null) {
  15. Entry<K,V> next = e.next;
  16. // 查找对应Entry
  17. if (h == e.hash && eq(k, e.get())) {
  18. modCount++;
  19. size--;
  20. if (prev == e)
  21. tab[i] = next;
  22. else
  23. prev.next = next;
  24. // 如果找到,返回对应Entry的value
  25. return e.value;
  26. }
  27. prev = e;
  28. e = next;
  29. }
  30. return null;
  31. }

8、WeakHashMap-源码分析-举例使用-Demo

  1. public class WeakHashMapTest {
  2. public static void main(String[] args){
  3. testWeakHashMap();
  4. }
  5. private static void testWeakHashMap() {
  6. // 创建3个String对象用来做key
  7. String w1 = new String("key1");
  8. String w2 = new String("key2");
  9. String w3 = new String("key3");
  10. // 新建WeakHashMap
  11. Map weakHashMap = new WeakHashMap();
  12. // 添加键值对
  13. weakHashMap.put(w1, "v1");
  14. weakHashMap.put(w2, "v2");
  15. weakHashMap.put(w3, "v3");
  16. // 打印出weakHashMap
  17. System.out.printf("weakHashMap:%s\n", weakHashMap); //{key1=w1, key2=w2, key3=w3}
  18. // containsKey(Object key) :是否包含键key
  19. System.out.printf("contains key key1 : %s\n",weakHashMap.containsKey("key1"));//true
  20. System.out.printf("contains key key4 : %s\n",weakHashMap.containsKey("key4"));//false
  21. // containsValue(Object value) :是否包含值value
  22. System.out.printf("contains value v1 : %s\n",weakHashMap.containsValue("v1"));//true
  23. System.out.printf("contains value 0 : %s\n",weakHashMap.containsValue(0));//false
  24. // remove(Object key) : 删除键key对应的键值对
  25. weakHashMap.remove("three");
  26. System.out.printf("weakHashMap: %s\n", weakHashMap);{key1=w1, key2=w2, key3=w3}
  27. // ---- 测试 WeakHashMap 的自动回收特性 ----
  28. // 将w1设置null。
  29. // 这意味着“弱键”w1再没有被其它对象引用,调用gc时会回收WeakHashMap中与“w1”对应的键值对
  30. w1 = null;
  31. // 内存回收。这里,会回收WeakHashMap中与“w1”对应的键值对
  32. System.gc();
  33. // 遍历WeakHashMap
  34. Iterator iter = weakHashMap.entrySet().iterator();
  35. while (iter.hasNext()) {
  36. Map.Entry en = (Map.Entry)iter.next();
  37. /**
  38. next : key2 - w2
  39. next : key3 - w3
  40. */
  41. System.out.printf("next : %s - %s\n",en.getKey(),en.getValue());
  42. }
  43. // 打印WeakHashMap的实际大小
  44. // after gc WeakHashMap size:2
  45. System.out.printf("after gc WeakHashMap size:%s\n", weakHashMap.size());
  46. }
  47. }

9、WeakHashMap-源码分析-应用场景-Demo

  1. 1、应用场景
  2. 1-1、由于WeakHashMap可以自动清除Entry,所以比较适合用于存储非必需对象,用作缓存非常合适。
  3. 1-2、配合事务进行使用,存储事务过程中的各类信息,结构:
  4. WeakHashMap<String,Map<K,V>> transactionCache;
  5. 1-2-1keyString类型,可以用来标志区分不同的事务,起到一个事务id的作用。value是一个map,可以是一个简单的HashMap或者LinkedHashMap,用来存放在事务中需要使用到的信息。
  6. 1-2-2、在事务开始时创建一个事务id,并用它来作为key,事务结束后,将这个强引用消除掉,这样既能保证在事务中可以获取到所需要的信息,又能自动释放掉map中的所有信息。
  7. 2、应用Demo-在Tomcat的工具类里,有这样一种实现。基于LRU策略。
  8. package org.apache.tomcat.util.collections;
  9. public final class ConcurrentCache<K,V> {
  10. private final int size;
  11. private final Map<K,V> eden;
  12. private final Map<K,V> longterm;
  13. public ConcurrentCache(int size) {
  14. this.size = size;
  15. this.eden = new ConcurrentHashMap<>(size);
  16. this.longterm = new WeakHashMap<>(size);
  17. }
  18. public V get(K k) {
  19. V v = this.eden.get(k);
  20. if (v == null) {
  21. synchronized (longterm) {
  22. v = this.longterm.get(k);
  23. }
  24. if (v != null) {
  25. this.eden.put(k, v);
  26. }
  27. }
  28. return v;
  29. }
  30. public void put(K k, V v) {
  31. if (this.eden.size() >= size) {
  32. synchronized (longterm) {
  33. this.longterm.putAll(this.eden);
  34. }
  35. this.eden.clear();
  36. }
  37. this.eden.put(k, v);
  38. }
  39. }