WeakHashMap 源码分析
WeakHashMap
WeakHashMap 能解决什么问题?什么时候使用 WeakHashMap?
1)WeakHashMap 是基于弱引用键实现 Map 接口的哈希表。当内存紧张,并且键只被 WeakHashMap 使用时,垃圾回收器会按需回收键值对。
2)WeakHashMap 支持 null 键和 null 值。
3)WeakHashMap 是线程不同步的,可以通过 {@link Collections#synchronizedMap Collections.synchronizedMap} 方法获取线程同步的 Map。
如何使用 WeakHashMap?
1)可以使用 WeakHashMap 实现热点分代缓存,当内存紧张,并且键只被 WeakHashMap 使用时,垃圾回收器会按需回收键值对。
使用 WeakHashMap 有什么风险?
1)WeakHashMap 读写数据时,都会同步锁住引用队列来删除无效的节点,当 JVM 内存不够而频繁执行垃圾回收时,同步删除操作比较影响性能。
WeakHashMap 核心操作的实现原理?
- 创建实例
/**
* 默认初始容量值为 16
*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* 最大的容量值,即 bucket 的个数
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的加载因子
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 底层存储键值对的 table
*/
Entry<K,V>[] table;
/**
* 已有键值对总数
*/
private int size;
/**
* 下一次扩容的阈值
*/
private int threshold;
/**
* 加载因子
*/
private final float loadFactor;
/**
* 当弱引用关联的值需要被 GC 垃圾回收时,该弱引用就会被加入到其关联的引用队列中,
* queue 中的对象都是 WeakHashMap 中需要被回收的节点。
*/
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/**
* 结构化修改的次数,用于实现 Fast-Fail
*/
int modCount;
/**
* WeakHashMap 的键值对继承了 WeakReference 类,可以实现按需回收
*/
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;
}
}
/**
* 创建一个容量为 16,加载因子为 0.75 的空 WeakHashMap 实例
*/
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* 创建一个容量为大于等于 initialCapacity 的最小的 2 的幂,
* 加载因子为 0.75 的空 WeakHashMap 实例
*/
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 创建一个容量为大于等于 initialCapacity 的最小的 2 的幂,
* 加载因子为 loadFactor 的空 WeakHashMap 实例
*/
public WeakHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
}
// 初始化容量超出最大容量值
if (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;
}
// 初始化 table
table = newTable(capacity);
// 写入加载因子
this.loadFactor = loadFactor;
// 写入扩容阈值
threshold = (int)(capacity * loadFactor);
}
- 添加键值对
/**
* 添加新的键值对
*/
@Override
public V put(K key, V value) {
// mask 键
final Object k = WeakHashMap.maskNull(key);
// 计算哈希值
final int h = hash(k);
// 去除无效的键值对,并返回 table
final Entry<K,V>[] tab = getTable();
// 基于键的哈希值计算目标索引
final int i = WeakHashMap.indexFor(h, tab.length);
// 读取指定的 bucket,并遍历单向链表的所有元素
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
/**
* 当前节点的哈希值和目标哈希值一致,
* 并且目标键和弱引用键关联的键值相等
*/
if (h == e.hash && WeakHashMap.eq(k, e.get())) {
final V oldValue = e.value;
// 如果新值和旧值不相等,则替换旧值
if (value != oldValue) {
e.value = value;
}
// 返回旧值
return oldValue;
}
}
modCount++;
// 读取 bucket 首节点
final Entry<K,V> e = tab[i];
// 创建新的节点作为 bucket 的首节点,并将原来的单向链表链接在其后
tab[i] = new Entry<>(k, value, queue, h, e);
// 递增元素总个数,如果超出阈值
if (++size >= threshold) {
// 进行双倍扩容
resize(tab.length * 2);
}
// 新增节点返回 null
return null;
}
/**
* 删除 WeakHashMap 中的无效节点,并返回 table
*/
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
/**
* 从 table 中删除过时的节点
*/
private void expungeStaleEntries() {
// 同步处理弱引用队列中的所有元素
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
final
Entry<K,V> e = (Entry<K,V>) x;
// 计算哈希值
final int i = WeakHashMap.indexFor(e.hash, table.length);
// 读取 bucket 的首节点
Entry<K,V> prev = table[i];
// 暂存前置节点
Entry<K,V> p = prev;
while (p != null) {
// 读取下一个节点
final Entry<K,V> next = p.next;
// 链表中的当前节点就是引用队列中弹出的节点
if (p == e) {
// 当前处理节点是 bucket 首节点
if (prev == e) {
// 更新 bucket 首节点为后置节点
table[i] = next;
} else {
// 前置节点的后置节点更新为处理节点的后置节点
prev.next = next;
}
// 将节点值置空
e.value = null; // Help GC
// 递减元素个数
size--;
break;
}
// 否则处理下一个节点
prev = p;
p = next;
}
}
}
}
void resize(int newCapacity) {
// 读取旧 table
final Entry<K,V>[] oldTable = getTable();
// 读取旧容量
final int oldCapacity = oldTable.length;
// 旧容量达到最大容量
if (oldCapacity == MAXIMUM_CAPACITY) {
// 只更新扩容阈值为 Integer.MAX_VALUE
threshold = Integer.MAX_VALUE;
return;
}
// 创建新 table
final Entry<K,V>[] newTable = newTable(newCapacity);
// 迁移旧 table 中的元素到新 table 中
transfer(oldTable, newTable);
table = newTable;
/*
* 总元素个数 >= 扩容阈值的二分之一
*/
if (size >= threshold / 2) {
// 计算新的阈值
threshold = (int)(newCapacity * loadFactor);
} else {
// 去除无效的节点并将元素迁移回旧 table 中
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
/**
* 从 src table 迁移所有的节点到 dest table
*/
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
// 顺序处理 src table 中的每个 bucket
for (int j = 0; j < src.length; ++j) {
// 读取首节点
Entry<K,V> e = src[j];
// 将其置空
src[j] = null;
// 首节点不为 null,表示当前 bucket 不为空
while (e != null) {
// 读取下一个节点
final Entry<K,V> next = e.next;
// 读取当前节点的键
final Object key = e.get();
// 键为 null,表示已经被回收,则删除该节点
if (key == null) {
e.next = null; // Help GC
e.value = null; // " "
size--;
} else {
// 计算键在新 bucket 中的索引
final int i = WeakHashMap.indexFor(e.hash, dest.length);
// 当前节点的 next 指向新 bucket 的首节点
e.next = dest[i];
/**
* 新 bucket 的首节点更新为当前节点,
* 每次添加节点,新节点都作为 bucket 的首节点加入到单向链表中
*/
dest[i] = e;
}
// 递归处理单向链表的下一个节点
e = next;
}
}
}
- 读取值
/**
* 根据键读取值
*/
@Override
public V get(Object key) {
final Object k = WeakHashMap.maskNull(key);
final int h = hash(k);
final Entry<K,V>[] tab = getTable();
final int index = WeakHashMap.indexFor(h, tab.length);
// 读取 bucket 首节点
Entry<K,V> e = tab[index];
while (e != null) {
// 当前节点键和目标键相等
if (e.hash == h && WeakHashMap.eq(k, e.get())) {
// 读取值
return e.value;
}
e = e.next;
}
// 键不存在返回 null
return null;
}
- 读取元素个数
/**
* 去除无效的节点,并返回粗略的元素总数
*/
@Override
public int size() {
if (size == 0) {
return 0;
}
expungeStaleEntries();
return size;
}
- 是否为空
/**
* WeakHashMap 是否为空
*/
@Override
public boolean isEmpty() {
return size() == 0;
}
WeakHashMap 源码分析的更多相关文章
- 死磕 java集合之WeakHashMap源码分析
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时 ...
- WeakHashMap源码分析
WeakHashMap是一种弱引用map,内部的key会存储为弱引用, 当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉, 下一次当我们操作map的时候会把对应的Entry整个删 ...
- JDK源码分析(9)之 WeakHashMap 相关
平时我们使用最多的数据结构肯定是 HashMap,但是在使用的时候我们必须知道每个键值对的生命周期,并且手动清除它:但是如果我们不是很清楚它的生命周期,这时候就比较麻烦:通常有这样几种处理方式: 由一 ...
- Java集合源码分析(八)——WeakHashMap
简介 WeakHashMap 继承于AbstractMap,实现了Map接口. 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和 ...
- MyBatis源码分析(3)—— Cache接口以及实现
@(MyBatis)[Cache] MyBatis源码分析--Cache接口以及实现 Cache接口 MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口. ...
- spring源码分析之spring-core总结篇
1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...
- cglib源码分析(一): 缓存和KEY
cglib是一个java 字节码的生成工具,它是对asm的进一步封装,提供了一系列class generator.研究cglib主要是因为它也提供了动态代理功能,这点和jdk的动态代理类似. 一. C ...
- Java Reference 源码分析
@(Java)[Reference] Java Reference 源码分析 Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使 ...
- Hessian源码分析--HessianProxy
在上一篇博客 Hessian源码分析--HessianProxyFactory 中我们了解到,客户端获得的对象其实是HessianProxy生成的目标对象,当调用目标对象的方法时,会调用Hessian ...
随机推荐
- 有序无序Ul->Li Ol->Li菜单,默认点击当前弹出下拉,再次点击收起下拉菜单(变形2 ---修饰)
从上面可以看出,两个问题,第一:下拉出现的太快太突然,第二:再点击下一个下拉菜单的时候,上一个不会闭合,针对这两个问题,接下来会一 一解决. 解决下拉太快: js中有个jquery效果,有一个效果是j ...
- Scrapy 教程(十)-管道与数据库
Scrapy 框架将爬取的数据通过管道进行处理,即 pipelines.py 文件. 管道处理流程 一.定义 item item 表示的是数据结构,定义了数据包括哪些字段 class TianqiIt ...
- I-最短的名字
在一个奇怪的村子中,很多人的名字都很长,比如aaaaa, bbb and abababab. 名字这么长,叫全名显然起来很不方便.所以村民之间一般只叫名字的前缀.比如叫’aaaaa’的时候可以只叫’a ...
- BZOJ 5317: [Jsoi2018]部落战争
传送门 写出式子,若存在 $a \in A$,$b \in B$,使得 $b+v=a$,那么此方案会产生冲突 即存在 $a \in A$,$b \in B$,使得 $v=a+(-b)$,设 $C=A+ ...
- go & RabbitMQ
参考 RabbitMQ tutorial - 官方示例 Go code for RabbitMQ tutorials - 官方示例源码 go语言开发RabbitMQ-牛刀小小试试
- js/nodejs导入Excel相关
导入示例如下: Excel可设置单元格的数字显示格式,特别的,常规格式下,会根据列宽缩进显示. 实际中,有时需要导入实际值,有时需要导入显示值. 而B2的显示值,由于跟列宽相关,目前未找到任何软件,可 ...
- uni-app中页面部分内容使用索引列表(uni-indexed-list),动态数据
一.引入uni-indexed-list.uni-icons组件 从uni-app插件市场下载或从HBuilder X提供的hello uni-app模板中复制 二.页面中引用 三.对请求获得的数据处 ...
- nice - 改变执行程序的优先级
总览 (SYNOPSIS) nice [OPTION]... [COMMAND [ARG]...] 描述 (DESCRIPTION) 以 调整过的 调度优先级 运行 COMMAND. 如果 没给出 C ...
- windows使用cmd命令输出文件清单和文件树
输出目录树:tree /f > d:\filetree.txt 输出目录清单:dir /s /b > d:\filelist.txt
- 管线命令(Pipe)
管线命令接受|前面传来的stdout,管线示意图如下所示: 管线两个需要注意的地方: 1.管线仅会处理stdout,忽略对stderr的处理 2.管线必须接受前个指令的stdin才是 那么,如果我想接 ...