WeakHashMap

WeakHashMap 能解决什么问题?什么时候使用 WeakHashMap?

  1. 1WeakHashMap 是基于弱引用键实现 Map 接口的哈希表。当内存紧张,并且键只被 WeakHashMap 使用时,垃圾回收器会按需回收键值对。
  2. 2WeakHashMap 支持 null 键和 null 值。
  3. 3WeakHashMap 是线程不同步的,可以通过 {@link Collections#synchronizedMap Collections.synchronizedMap} 方法获取线程同步的 Map。

如何使用 WeakHashMap?

  1. 1)可以使用 WeakHashMap 实现热点分代缓存,当内存紧张,并且键只被 WeakHashMap 使用时,垃圾回收器会按需回收键值对。

使用 WeakHashMap 有什么风险?

  1. 1WeakHashMap 读写数据时,都会同步锁住引用队列来删除无效的节点,当 JVM 内存不够而频繁执行垃圾回收时,同步删除操作比较影响性能。

WeakHashMap 核心操作的实现原理?

  • 创建实例
  1. /**
  2. * 默认初始容量值为 16
  3. */
  4. private static final int DEFAULT_INITIAL_CAPACITY = 16;
  5. /**
  6. * 最大的容量值,即 bucket 的个数
  7. */
  8. private static final int MAXIMUM_CAPACITY = 1 << 30;
  9. /**
  10. * 默认的加载因子
  11. */
  12. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  13. /**
  14. * 底层存储键值对的 table
  15. */
  16. Entry<K,V>[] table;
  17. /**
  18. * 已有键值对总数
  19. */
  20. private int size;
  21. /**
  22. * 下一次扩容的阈值
  23. */
  24. private int threshold;
  25. /**
  26. * 加载因子
  27. */
  28. private final float loadFactor;
  29. /**
  30. * 当弱引用关联的值需要被 GC 垃圾回收时,该弱引用就会被加入到其关联的引用队列中,
  31. * queue 中的对象都是 WeakHashMap 中需要被回收的节点。
  32. */
  33. private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
  34. /**
  35. * 结构化修改的次数,用于实现 Fast-Fail
  36. */
  37. int modCount;
  38. /**
  39. * WeakHashMap 的键值对继承了 WeakReference 类,可以实现按需回收
  40. */
  41. private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
  42. /**
  43. * 节点目标值
  44. */
  45. V value;
  46. /**
  47. * 节点哈希值
  48. */
  49. final int hash;
  50. /**
  51. * 下一个节点
  52. */
  53. Entry<K,V> next;
  54. /**
  55. * Creates new entry.
  56. */
  57. Entry(Object key, V value,
  58. ReferenceQueue<Object> queue,
  59. int hash, Entry<K,V> next) {
  60. // 键就是弱引用关联的值
  61. super(key, queue);
  62. this.value = value;
  63. this.hash = hash;
  64. this.next = next;
  65. }
  66. }
  67. /**
  68. * 创建一个容量为 16,加载因子为 0.75 的空 WeakHashMap 实例
  69. */
  70. public WeakHashMap() {
  71. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  72. }
  73. /**
  74. * 创建一个容量为大于等于 initialCapacity 的最小的 2 的幂,
  75. * 加载因子为 0.75 的空 WeakHashMap 实例
  76. */
  77. public WeakHashMap(int initialCapacity) {
  78. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  79. }
  80. /**
  81. * 创建一个容量为大于等于 initialCapacity 的最小的 2 的幂,
  82. * 加载因子为 loadFactor 的空 WeakHashMap 实例
  83. */
  84. public WeakHashMap(int initialCapacity, float loadFactor) {
  85. if (initialCapacity < 0) {
  86. throw new IllegalArgumentException("Illegal Initial Capacity: "+
  87. initialCapacity);
  88. }
  89. // 初始化容量超出最大容量值
  90. if (initialCapacity > MAXIMUM_CAPACITY) {
  91. initialCapacity = MAXIMUM_CAPACITY;
  92. }
  93. // 加载因子非法
  94. if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
  95. throw new IllegalArgumentException("Illegal Load factor: "+
  96. loadFactor);
  97. }
  98. // 获取大于等于 initialCapacity 的最小的 2 的幂
  99. int capacity = 1;
  100. while (capacity < initialCapacity) {
  101. capacity <<= 1;
  102. }
  103. // 初始化 table
  104. table = newTable(capacity);
  105. // 写入加载因子
  106. this.loadFactor = loadFactor;
  107. // 写入扩容阈值
  108. threshold = (int)(capacity * loadFactor);
  109. }
  • 添加键值对
  1. /**
  2. * 添加新的键值对
  3. */
  4. @Override
  5. public V put(K key, V value) {
  6. // mask 键
  7. final Object k = WeakHashMap.maskNull(key);
  8. // 计算哈希值
  9. final int h = hash(k);
  10. // 去除无效的键值对,并返回 table
  11. final Entry<K,V>[] tab = getTable();
  12. // 基于键的哈希值计算目标索引
  13. final int i = WeakHashMap.indexFor(h, tab.length);
  14. // 读取指定的 bucket,并遍历单向链表的所有元素
  15. for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
  16. /**
  17. * 当前节点的哈希值和目标哈希值一致,
  18. * 并且目标键和弱引用键关联的键值相等
  19. */
  20. if (h == e.hash && WeakHashMap.eq(k, e.get())) {
  21. final V oldValue = e.value;
  22. // 如果新值和旧值不相等,则替换旧值
  23. if (value != oldValue) {
  24. e.value = value;
  25. }
  26. // 返回旧值
  27. return oldValue;
  28. }
  29. }
  30. modCount++;
  31. // 读取 bucket 首节点
  32. final Entry<K,V> e = tab[i];
  33. // 创建新的节点作为 bucket 的首节点,并将原来的单向链表链接在其后
  34. tab[i] = new Entry<>(k, value, queue, h, e);
  35. // 递增元素总个数,如果超出阈值
  36. if (++size >= threshold) {
  37. // 进行双倍扩容
  38. resize(tab.length * 2);
  39. }
  40. // 新增节点返回 null
  41. return null;
  42. }
  43. /**
  44. * 删除 WeakHashMap 中的无效节点,并返回 table
  45. */
  46. private Entry<K,V>[] getTable() {
  47. expungeStaleEntries();
  48. return table;
  49. }
  50. /**
  51. * 从 table 中删除过时的节点
  52. */
  53. private void expungeStaleEntries() {
  54. // 同步处理弱引用队列中的所有元素
  55. for (Object x; (x = queue.poll()) != null; ) {
  56. synchronized (queue) {
  57. @SuppressWarnings("unchecked")
  58. final
  59. Entry<K,V> e = (Entry<K,V>) x;
  60. // 计算哈希值
  61. final int i = WeakHashMap.indexFor(e.hash, table.length);
  62. // 读取 bucket 的首节点
  63. Entry<K,V> prev = table[i];
  64. // 暂存前置节点
  65. Entry<K,V> p = prev;
  66. while (p != null) {
  67. // 读取下一个节点
  68. final Entry<K,V> next = p.next;
  69. // 链表中的当前节点就是引用队列中弹出的节点
  70. if (p == e) {
  71. // 当前处理节点是 bucket 首节点
  72. if (prev == e) {
  73. // 更新 bucket 首节点为后置节点
  74. table[i] = next;
  75. } else {
  76. // 前置节点的后置节点更新为处理节点的后置节点
  77. prev.next = next;
  78. }
  79. // 将节点值置空
  80. e.value = null; // Help GC
  81. // 递减元素个数
  82. size--;
  83. break;
  84. }
  85. // 否则处理下一个节点
  86. prev = p;
  87. p = next;
  88. }
  89. }
  90. }
  91. }
  92. void resize(int newCapacity) {
  93. // 读取旧 table
  94. final Entry<K,V>[] oldTable = getTable();
  95. // 读取旧容量
  96. final int oldCapacity = oldTable.length;
  97. // 旧容量达到最大容量
  98. if (oldCapacity == MAXIMUM_CAPACITY) {
  99. // 只更新扩容阈值为 Integer.MAX_VALUE
  100. threshold = Integer.MAX_VALUE;
  101. return;
  102. }
  103. // 创建新 table
  104. final Entry<K,V>[] newTable = newTable(newCapacity);
  105. // 迁移旧 table 中的元素到新 table 中
  106. transfer(oldTable, newTable);
  107. table = newTable;
  108. /*
  109. * 总元素个数 >= 扩容阈值的二分之一
  110. */
  111. if (size >= threshold / 2) {
  112. // 计算新的阈值
  113. threshold = (int)(newCapacity * loadFactor);
  114. } else {
  115. // 去除无效的节点并将元素迁移回旧 table 中
  116. expungeStaleEntries();
  117. transfer(newTable, oldTable);
  118. table = oldTable;
  119. }
  120. }
  121. /**
  122. * 从 src table 迁移所有的节点到 dest table
  123. */
  124. private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
  125. // 顺序处理 src table 中的每个 bucket
  126. for (int j = 0; j < src.length; ++j) {
  127. // 读取首节点
  128. Entry<K,V> e = src[j];
  129. // 将其置空
  130. src[j] = null;
  131. // 首节点不为 null,表示当前 bucket 不为空
  132. while (e != null) {
  133. // 读取下一个节点
  134. final Entry<K,V> next = e.next;
  135. // 读取当前节点的键
  136. final Object key = e.get();
  137. // 键为 null,表示已经被回收,则删除该节点
  138. if (key == null) {
  139. e.next = null; // Help GC
  140. e.value = null; // " "
  141. size--;
  142. } else {
  143. // 计算键在新 bucket 中的索引
  144. final int i = WeakHashMap.indexFor(e.hash, dest.length);
  145. // 当前节点的 next 指向新 bucket 的首节点
  146. e.next = dest[i];
  147. /**
  148. * 新 bucket 的首节点更新为当前节点,
  149. * 每次添加节点,新节点都作为 bucket 的首节点加入到单向链表中
  150. */
  151. dest[i] = e;
  152. }
  153. // 递归处理单向链表的下一个节点
  154. e = next;
  155. }
  156. }
  157. }
  • 读取值
  1. /**
  2. * 根据键读取值
  3. */
  4. @Override
  5. public V get(Object key) {
  6. final Object k = WeakHashMap.maskNull(key);
  7. final int h = hash(k);
  8. final Entry<K,V>[] tab = getTable();
  9. final int index = WeakHashMap.indexFor(h, tab.length);
  10. // 读取 bucket 首节点
  11. Entry<K,V> e = tab[index];
  12. while (e != null) {
  13. // 当前节点键和目标键相等
  14. if (e.hash == h && WeakHashMap.eq(k, e.get())) {
  15. // 读取值
  16. return e.value;
  17. }
  18. e = e.next;
  19. }
  20. // 键不存在返回 null
  21. return null;
  22. }
  • 读取元素个数
  1. /**
  2. * 去除无效的节点,并返回粗略的元素总数
  3. */
  4. @Override
  5. public int size() {
  6. if (size == 0) {
  7. return 0;
  8. }
  9. expungeStaleEntries();
  10. return size;
  11. }
  • 是否为空
  1. /**
  2. * WeakHashMap 是否为空
  3. */
  4. @Override
  5. public boolean isEmpty() {
  6. return size() == 0;
  7. }

WeakHashMap 源码分析的更多相关文章

  1. 死磕 java集合之WeakHashMap源码分析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时 ...

  2. WeakHashMap源码分析

    WeakHashMap是一种弱引用map,内部的key会存储为弱引用, 当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉, 下一次当我们操作map的时候会把对应的Entry整个删 ...

  3. JDK源码分析(9)之 WeakHashMap 相关

    平时我们使用最多的数据结构肯定是 HashMap,但是在使用的时候我们必须知道每个键值对的生命周期,并且手动清除它:但是如果我们不是很清楚它的生命周期,这时候就比较麻烦:通常有这样几种处理方式: 由一 ...

  4. Java集合源码分析(八)——WeakHashMap

    简介 WeakHashMap 继承于AbstractMap,实现了Map接口. 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和 ...

  5. MyBatis源码分析(3)—— Cache接口以及实现

    @(MyBatis)[Cache] MyBatis源码分析--Cache接口以及实现 Cache接口 MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口. ...

  6. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  7. cglib源码分析(一): 缓存和KEY

    cglib是一个java 字节码的生成工具,它是对asm的进一步封装,提供了一系列class generator.研究cglib主要是因为它也提供了动态代理功能,这点和jdk的动态代理类似. 一. C ...

  8. Java Reference 源码分析

    @(Java)[Reference] Java Reference 源码分析 Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使 ...

  9. Hessian源码分析--HessianProxy

    在上一篇博客 Hessian源码分析--HessianProxyFactory 中我们了解到,客户端获得的对象其实是HessianProxy生成的目标对象,当调用目标对象的方法时,会调用Hessian ...

随机推荐

  1. Javascript高级面试

    原型 异步 一.什么是单线程,和异步有什么关系 单线程:只有一个线程,同一时间只能做一件事原因:避免DOM渲染的冲突解决方案:异步 为什么js只有一个线程:避免DOM渲染冲突 浏览器需要渲染DOM J ...

  2. winform 窗体间传值

    WinForm 两窗体之间传值实例 2010-12-27 22:10:11|  分类: 学业|举报|字号 订阅     下载LOFTER我的照片书  |     窗体Form1和Form2 Form2 ...

  3. SpringBoot_03mybatisPlus

    注意: mybatisPlus默认加载resources下的mapper文件夹下的xml文件 默认将数据库表的字段用驼峰标识转换成实体类的属性 官方网站: https://mp.baomidou.co ...

  4. k3 cloud中单据体中文本自适应

    在单据体中添加多行文本,然后设置本地配置,只读单元格自动换行

  5. ELK报错及解决方案

    ELK报错及解决方案 1.jdk版本问题 报错如下: future versions of Elasticsearch will require Java 11; your Java version ...

  6. 028-实现阿里云ESC多FLAT网络

    实现类似于阿里云ECS的网络结构,其效果为云主机拥有两块和两个不同的网络,一个网络是用于用于和外网连接,一个用于内网通信,但宿主机上至少有两个网卡,整体配置如下:1.在wmare里给宿主机添加一块网卡 ...

  7. 牛客练习赛14 B 区间的连续段 (倍增)

    链接:https://ac.nowcoder.com/acm/contest/82/B来源:牛客网 区间的连续段 时间限制:C/C++ 7秒,其他语言14秒 空间限制:C/C++ 262144K,其他 ...

  8. 感想 - 猴子刷视频app

    看到一个视频,内容是一只猿猴熟练地像人一样刷短视频app,惟妙惟肖:https://pan.baidu.com/s/10-eibLmuybKtRJ-CKnruYA 抽象思考和语言才是人类独有的能力,视 ...

  9. Codeforces 962 /2错误 相间位置排列 堆模拟 X轴距离最小值 前向星点双连通分量求只存在在一个简单环中的边

    A #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) #def ...

  10. [HTTP知识体系]前端常用的一些参数

    1.http常见状态码(status code) 200(成功) 服务器已成功处理了请求.通常,这表示服务器提供了请求的网页. 301 (永久移动) 请求的网页已永久移动到新位置. 服务器返回此响应( ...