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 源码分析的更多相关文章

  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. 工作笔记之20170223:①关于Html5的placeholder属性,②以及input的outline:none的样式问题

    关于这边几个样式问题,重点有这么几个: (1)placeholder="请输入密码" (2) color:#BEB6B6; border:0px; border-bottom:1p ...

  2. linux安装mysql8(完整图文笔记)

    基本命令 安装 : yum install mysql-community-server 启动 : service mysqld start/restart 停止 : service mysqld s ...

  3. 在django中使用循环与条件语言

    {% if not Article_type_id %} <li class="active"><a href="/">全部</a ...

  4. 表格变色示例中发现的问题——attr()与prop()

    在练习jQuery表格变色例子过程中,发现了一下几个问题: 在IEEdge浏览器中切换选中行会出现上一个表格行背景色被吃掉的情况: 在chrome中从上向下单击行中任意单元可以选中该行,而从下往上单击 ...

  5. CentOS上安装Git及配置远程仓库

    首先登陆CentOS服务器,连接上服务器之后我们使用yum remove git 命令删除已安装的Git,若之前没安装过Git则不需要这一步.注意前提是你的CentOS服务器上安装了yum,这是Cen ...

  6. MixConv

    深度分离卷积一般使用的是3*3的卷积核,这篇论文在深度分离卷积时使用了多种卷积核,并验证了其有效性 1.大的卷积核能提高模型的准确性,但也不是越大越好.如下,k=9时,精度逐渐降低 2. mixCon ...

  7. mobilefacenet

    insightface作者训练的mobileFaceNet:    https://github.com/deepinsight/insightface/issues/214 ncnn的转换:http ...

  8. C#基础知识之依赖注入

    目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依赖注入 ...

  9. Attention Points

    Attention Points 数组范围 无向图.树,边表的范围是边数的两倍. 因为最近树的题目做的比较多,一定要注意分清是树还是图,不能冲上去就去开struct Edge{int to,ne,w; ...

  10. ubuntu16.04 下安装jdk

    1.         在/usr/lib目录下新建jdk mkdir jdk 2.         执行文件移动 sudo mv ./resource/jdk-8u191-linux-x64.tar. ...