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 ...
随机推荐
- Python win32com模块 合并文件夹内多个docx文件为一个docx
Python win32com模块 合并文件夹内多个docx文件为一个docx #!/usr/bin/env python # -*- coding: utf-8 -*- from win32com. ...
- UvaLive 6664 Clock Hands
链接:http://vjudge.net/problem/viewProblem.action?id=49409 题意:给一个奇怪的能够记录N小时内时间的表(生活中的表是12小时计时的). 而且给出一 ...
- 分布式系统中唯一 ID 的生成方法
在分布式系统存在多个 Shard 的场景中, 同时在各个 Shard 插入数据时, 怎么给这些数据生成全局的 unique ID? 在单机系统中 (例如一个 MySQL 实例), unique ID ...
- centos7修改端口登陆
1 安装ssh服务 yum install -y openssh-server2 修改默认端口并保存 vim /etc/ssh/sshd_config 3 打开防火墙 service firewall ...
- 剑指offer-二叉树的下一结点-树-python
题目描述 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回.注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针. 思路:中序遍历,pNode节点的下一个节点根据中序 ...
- Site error: the ionCube PHP Loader needs to be installed.解决办法
问题描述: 有些模块的作者为了保护代码而采用ionCube加密的代码,所以这里必须给服务器装上这个php的扩展,就好像以前的zend一样 解决办法: http://bbs.52jscn.com/thr ...
- 十大基本功之testbench
1. 激励的产生 对于testbench而言,端口应当和被测试的module一一对应.端口分为input,output和inout类型产生激励信号的时候,input对应的端口应当申明为reg, o ...
- JavaScript设计模式样例四 —— 单例模式
单例模式(Singleton Pattern): 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 目的:阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例. 场景: ...
- html2canvas 把h5网页保存为图片 区域保存
html2canvas 把h5网页保存为图片 想把一个网页得某些元素,绘制成图片保存,有些数据是接口动态加载的,所以不能UI给到图片,需要我们把api的数据也绘制到图片上 html2canvas这个插 ...
- 在国外搭建 Web 服务器 - Linode VPS
在国外搭建 Web 服务器 - Linode VPS 买一台虚拟服务器(VPS),把你网站放在上面跑跑,找找感觉,平时也可以用它来练习.前几天,搜索到了有人推荐 Linode 的 VPS,昨天又有朋友 ...