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. 配置ssh免密码登录设置后还是提示需要输入密码

    工作之余搭建了一个集群测试,配置了ssh免密码登录以后  ,所有的ssh-copy-id 密钥也都分发了 ,各项配置也没有问题,但是使用ssh进行免密登录时,没有报错,但是要输入被ssh主机的登录密码 ...

  2. 你不知道的props和state

    State 与 Props 区别props 是组件对外的接口,state 是组件对内的接口.组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法 ...

  3. 混合加密算法(RSA和DES)

    一.混合加密的理由 a.前面提及了RSA加解密算法和DES加解密算法这两种加解密算法,由于随着计算机系统能力的不断发展,DES的安全性比它刚出现时会弱得多,追溯历史破解DES的案例层出不穷,一台实际的 ...

  4. python Rabbitmq编程(一)

    python Rabbitmq编程(一) 实现最简单的队列通信 send端 #!/usr/bin/env python import pika credentials = pika.PlainCred ...

  5. Symbol的isConcatSpreadable方法

    Symbol.isConcatSpreadable 布尔值,对象用于Array.prototype.concat()时,是否可以展开 let arr1 = ['c', 'd']; ['a', 'b'] ...

  6. JVM Direct Memory

    JVM除了堆内存.栈内存,还有DirectMemory内存,DirectMemory是java nio引入的. 在JDK1.4中新加入了NIO(New INput/Output)类,引入了一种基于通道 ...

  7. python 删除/app/*/logs/*/*.logs指定多少天的文件

    # encoding: utf-8 import sys import getopt import os import glob import time import datetime def rem ...

  8. 如何提高SMTP邮件的安全性?从而不被黑客窃听

    简单邮件传输协议(SMTP)用于在邮件服务器之间进行邮件传输,并且传统上是不安全的,因此容易被黑客窃听.命名实体的基于DNS的认证(国家统计局)用于SMTP提供了邮件传输更安全的方法,并逐渐变得越来越 ...

  9. shell之文本过滤(正则表达式)

    shell之文本过滤(正则表达式) 分类: linux shell脚本学习2012-09-14 12:59 213人阅读 评论(0) 收藏 举报 当从一个文件或命令输出中抽取或过滤文本时,可以使用正则 ...

  10. C++ GUI Qt4编程-创建自定义窗口部件

    C++ GUI Qt4编程-创建自定义窗口部件   Qtqt4 通过Qt窗口部件进行子类化或者直接对QWidget进行子类化,就可以创建自定义窗口部件,下面示范两种方式,并且也会说明如何把自定义窗口部 ...