WeakHashMap 源码解析

目录

一. 前言

二. 源码解析

2.1. 类结构

2.2. 成员变量

2.3. 构造方法

2.4. Entry

2.5. 添加元素

2.6. 扩容

2.7. 删除元素

2.8. 获取元素


一. 前言

    WeakHashMap,从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的entry可能会被GC自动删除,即使程序员没有调用remove()或者clear()方法。

    更直观的说,当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况:
1. 调用两次size()方法返回不同的值;
2. 两次调用isEmpty()方法,第一次返回false,第二次返回true;
3. 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
4. 两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。

    遇到这么奇葩的现象,你是不是觉得使用者一定会疯掉?其实不然,WeakHashMap 的这个特点特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到。

    要明白 WeakHashMap 的工作原理,还需要引入一个概念:弱引用(WeakReference)。我们都知道Java中的内存是通过GC自动管理的,GC会在程序运行过程中自动判断哪些对象是可以被回收的,并在合适的时机进行内存释放。GC判断某个对象是否可被回收的依据是,是否有有效的引用指向该对象。如果没有有效引用指向该对象(基本意味着不存在访问该对象的方式),那么该对象就是可回收的。这里的有效引用并不包括弱引用。也就是说,虽然弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收。

    WeakHashMap 内部是通过弱引用来管理entry的,弱引用的特性对应到 WeakHashMap 上意味着什么呢?将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收,除非在 WeakHashMap 之外还有对该key的强引用。

关于强引用,弱引用等概念请参见《Java 四种引用类型》。

二. 源码解析

    如果你看过前几篇关于 Map 和 Set 的讲解,一定会问:既然有 WeakHashMap,是否有 WeekHashSet 呢?答案是没有。不过Java Collections工具类给出了解决方案,Collections.newSetFromMap(Map<E,Boolean> map) 方法可以将任何 Map包装成一个Set。通过如下方式可以快速得到一个 WeakHashSet:

// 将WeakHashMap包装成一个Set
Set<Object> weakHashSet = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());// Collections.newSetFromMap()用于将任何Map包装成一个Set
public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {return new SetFromMap<>(map);
}private static class SetFromMap<E> extends AbstractSet<E>implements Set<E>, Serializable
{private final Map<E, Boolean> m;  // The backing mapprivate transient Set<E> s;       // Its keySetSetFromMap(Map<E, Boolean> map) {if (!map.isEmpty())throw new IllegalArgumentException("Map is non-empty");m = map;s = map.keySet();}public void clear()               {        m.clear(); }public int size()                 { return m.size(); }public boolean isEmpty()          { return m.isEmpty(); }public boolean contains(Object o) { return m.containsKey(o); }public boolean remove(Object o)   { return m.remove(o) != null; }public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }public Iterator<E> iterator()     { return s.iterator(); }public Object[] toArray()         { return s.toArray(); }public <T> T[] toArray(T[] a)     { return s.toArray(a); }public String toString()          { return s.toString(); }public int hashCode()             { return s.hashCode(); }public boolean equals(Object o)   { return o == this || s.equals(o); }public boolean containsAll(Collection<?> c) {return s.containsAll(c);}public boolean removeAll(Collection<?> c)   {return s.removeAll(c);}public boolean retainAll(Collection<?> c)   {return s.retainAll(c);}// addAll is the only inherited implementation// Override default methods in Collection@Overridepublic void forEach(Consumer<? super E> action) {s.forEach(action);}@Overridepublic boolean removeIf(Predicate<? super E> filter) {return s.removeIf(filter);}@Overridepublic Spliterator<E> spliterator() {return s.spliterator();}@Overridepublic Stream<E> stream()           {return s.stream();}@Overridepublic Stream<E> parallelStream()   {return s.parallelStream();}private static final long serialVersionUID = 2454657854757543876L;private void readObject(java.io.ObjectInputStream stream)throws IOException, ClassNotFoundException{stream.defaultReadObject();s = m.keySet();}
}

2.1. 类结构

从图中可以看出:WeakHashMap继承于AbstractMap,并且实现了Map接口。

2.2. 成员变量

// 默认的初始容量是16,必须是2的幂。
private static final int DEFAULT_INITIAL_CAPACITY = 16;// 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
private static final int MAXIMUM_CAPACITY = 1 << 30;// 默认加载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;// 存储数据的Entry数组,长度是2的幂。
// WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
private Entry[] table;// WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
private int size;// WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
private int threshold;// 加载因子实际大小
private final float loadFactor;// queue保存的是“已被GC清除”的“弱引用的键”。
// 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();// WeakHashMap被改变的次数
int modCount;

2.3. 构造方法

// 指定“容量大小”和“加载因子”的构造函数
public WeakHashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Initial Capacity: "+initialCapacity);// WeakHashMap的最大容量只能是MAXIMUM_CAPACITYif (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;// 创建Entry数组,用来保存数据table = newTable(capacity);// 设置“加载因子”this.loadFactor = loadFactor;// 设置“WeakHashMap阈值”,当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。threshold = (int)(capacity * loadFactor);
}// 指定“容量大小”的构造函数
public WeakHashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}// 默认构造函数。
public WeakHashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}// 包含“子Map”的构造函数
public WeakHashMap(Map<? extends K, ? extends V> m) {this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY),DEFAULT_LOAD_FACTOR);// 将m中的全部元素逐个添加到WeakHashMap中putAll(m);
}private Entry<K,V>[] newTable(int n) {return (Entry<K,V>[]) new Entry<?,?>[n];
}

2.4. Entry

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;}@SuppressWarnings("unchecked")public K getKey() {return (K) WeakHashMap.unmaskNull(get());}public V getValue() {return value;}public V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry<?,?> e = (Map.Entry<?,?>)o;K k1 = getKey();Object k2 = e.getKey();if (k1 == k2 || (k1 != null && k1.equals(k2))) {V v1 = getValue();Object v2 = e.getValue();if (v1 == v2 || (v1 != null && v1.equals(v2)))return true;}return false;}public int hashCode() {K k = getKey();V v = getValue();return Objects.hashCode(k) ^ Objects.hashCode(v);}public String toString() {return getKey() + "=" + getValue();}
}

可以看到Entry是继承WeakReference的,我们结合WeakReference再看一下:

public class WeakReference<T> extends Reference<T> {/*** Creates a new weak reference that refers to the given object.  The new* reference is not registered with any queue.* 创建一个新的弱应用给传入的对象,这个新的引用不注册任何队列** @param referent object the new weak reference will refer to*/public WeakReference(T referent) {super(referent);}/*** Creates a new weak reference that refers to the given object and is* registered with the given queue.* 创建一个新的弱应用给传入的对象,这个新的引用注册给一个给定的队列** @param referent object the new weak reference will refer to* @param q the queue with which the reference is to be registered,*          or <tt>null</tt> if registration is not required*/public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}
}

我们发现在WeakHashMap中把key注册给了WeakReference,也就是说在WeakHashMap中key是一个弱引用。

2.5. 添加元素

public V put(K key, V value) {// 如果key是null则给定一个空的对象进行修饰Object k = maskNull(key);// 计算key的hashint h = hash(k);// 获取tableEntry<K,V>[] tab = getTable();// 根据hash找到数组下标int i = indexFor(h, tab.length);// 找到链表中元素位置for (Entry<K,V> e = tab[i]; e != null; e = e.next) {if (h == e.hash && eq(k, e.get())) {V oldValue = e.value;if (value != oldValue)e.value = value;return oldValue;}}modCount++;Entry<K,V> e = tab[i];tab[i] = new Entry<>(k, value, queue, h, e);if (++size >= threshold) // 是否达到阀值达到阀值就扩容resize(tab.length * 2);return null;
}private Entry<K,V>[] getTable() {expungeStaleEntries();return table;
}private void expungeStaleEntries() {// 从 ReferenceQueue中拉取元素for (Object x; (x = queue.poll()) != null; ) {synchronized (queue) {@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) x;int i = indexFor(e.hash, table.length);Entry<K,V> prev = table[i];Entry<K,V> p = prev;while (p != null) {Entry<K,V> next = p.next;if (p == e) {if (prev == e)table[i] = next;elseprev.next = next;// Must not null out e.next;// stale entries may be in use by a HashIterator// 拿到entry的值赋值为null帮助GCe.value = null; // Help GCsize--;break;}prev = p;p = next;}}}
}

expungeStaleEntries 就是WeakHashMap的核心了,它承担着Map中死对象的清理工作。原理就是依赖WeakReference和ReferenceQueue的特性。在每个WeakHashMap都有个ReferenceQueue queue,在Entry初始化的时候也会将queue传给WeakReference,这样当某个key可以失去所有强引用之后,其key对应的WeakReference对象会被放到queue里,有了queue就知道需要清理哪些Entry了。

这里也是整个WeakHashMap里唯一加了同步的地方。除了上文说的到resize中调用了expungeStaleEntries(),size()中也调用了这个清理方法。另外 getTable()也调了,这就意味着几乎所有其他方法都间接调用了清理。

// 将"m"的全部元素都添加到WeakHashMap中
public void putAll(Map<? extends K, ? extends V> m) {int numKeysToBeAdded = m.size();if (numKeysToBeAdded == 0)return;// 计算容量是否足够,// 若“当前实际容量 < 需要的容量”,则将容量x2。if (numKeysToBeAdded > threshold) {int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);if (targetCapacity > MAXIMUM_CAPACITY)targetCapacity = MAXIMUM_CAPACITY;int newCapacity = table.length;while (newCapacity < targetCapacity)newCapacity <<= 1;if (newCapacity > table.length)resize(newCapacity);}// 将“m”中的元素逐个添加到WeakHashMap中。for (Map.Entry<? extends K, ? extends V> e : m.entrySet())put(e.getKey(), e.getValue());
}

2.6. 扩容

// 重新调整WeakHashMap的大小,newCapacity是调整后的单位
void resize(int newCapacity) {Entry[K, V] oldTable = getTable();int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}// 新建一个newTable,将“旧的table”的全部元素添加到“新的newTable”中,// 然后,将“新的newTable”赋值给“旧的table”。Entry[K, V] newTable = new Table(newCapacity);transfer(oldTable, newTable);table = newTable;if (size >= threshold / 2) {threshold = (int)(newCapacity * loadFactor);} else {// 删除table中“已被GC回收的key对应的键值对”expungeStaleEntries();transfer(newTable, oldTable);table = oldTable;}
}// 将WeakHashMap中的全部元素都添加到newTable中
private void transfer(Entry[] src, Entry[] dest) {for (int j = 0; j < src.length; ++j) {Entry<K,V> e = src[j];src[j] = null;while (e != null) {Entry<K,V> next = e.next;Object key = e.get();if (key == null) {e.next = null;  // Help GCe.value = null; //  "   "size--;} else {int i = indexFor(e.hash, dest.length);e.next = dest[i];dest[i] = e;}e = next;}}
}

2.7. 删除元素

// 删除“键为key”元素
public V remove(Object key) {Object k = maskNull(key);// 获取哈希值。int h = hash(k);Entry<K, V>[] tab = getTable();int i = indexFor(h, tab.length);Entry<K,V> prev = tab[i];Entry<K,V> e = prev;// 删除链表中“键为key”的元素// 本质是“删除单向链表中的节点”while (e != null) {Entry<K,V> next = e.next;if (h == e.hash && eq(k, e.get())) {modCount++;size--;if (prev == e)tab[i] = next;elseprev.next = next;return e.value;}prev = e;e = next;}return null;
}// 删除“键值对”
boolean removeMapping(Object o) {if (!(o instanceof Map.Entry))return false;Entry<K,V>[] tab = getTable();Map.Entry<?,?> entry = (Map.Entry<?,?>)o;Object k = maskNull(entry.getKey());int h = hash(k);int i = indexFor(h, tab.length);Entry<K,V> prev = tab[i];Entry<K,V> e = prev;// 删除链表中的“键值对e”// 本质是“删除单向链表中的节点”while (e != null) {Entry<K,V> next = e.next;if (h == e.hash && e.equals(entry)) {modCount++;size--;if (prev == e)tab[i] = next;elseprev.next = next;return e;}prev = e;e = next;}return false;
}// 清空WeakHashMap,将所有的元素设为null
public void clear() {while (queue.poll() != null);modCount++;Arrays.fill(table, null);size = 0;while (queue.poll() != null);
}

2.8. 获取元素

// 获取key对应的value
public V get(Object key) {Object k = maskNull(key);// 获取key的hash值。int h = hash(k);Entry<K,V>[] tab = getTable();int index = indexFor(h, tab.length);Entry<K,V> e = tab[index];// 在“该hash值对应的链表”上查找“键值等于key”的元素while (e != null) {if (e.hash == h && eq(k, e.get()))return e.value;e = e.next;}return null;
}// WeakHashMap是否包含key
public boolean containsKey(Object key) {return getEntry(key) != null;
}// 返回“键为key”的键值对
Entry<K,V> getEntry(Object key) {Object k = maskNull(key);int h = hash(k);Entry<K,V>[] tab = getTable();int index = indexFor(h, tab.length);Entry<K,V> e = tab[index];while (e != null && !(e.hash == h && eq(k, e.get())))e = e.next;return e;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/118899.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新手投资如何分配股票仓位?诺奖得主的秘诀是什么?| 附代码【邢不行】

2023年6月22日&#xff0c;诺贝尔经济学奖得主哈里.马克维茨于美国去世&#xff0c;享年95岁。 作为现代金融先驱者&#xff0c;马科维茨不仅是将数学引入金融的第一人&#xff0c;更用数学解释了分散投资的重要性。 更令人惊叹的是&#xff0c;过去十几年中如果按他的理论在中…

docker部署prometheus+grafana服务器监控(一)

docker-compose 部署prometheusgrafana Prometheus Prometheus 是有 SoundCloud 开发的开源监控系统和时序数据库&#xff0c;基于 Go 语言开发。通过基于 HTTP 的 pull 方式采集时序数据&#xff0c;通过服务发现或静态配置去获取要采集的目标服务器&#xff0c;支持多节点工…

18.2 使用NPCAP库抓取数据包

NPCAP 库是一种用于在Windows平台上进行网络数据包捕获和分析的库。它是WinPcap库的一个分支&#xff0c;由Nmap开发团队开发&#xff0c;并在Nmap软件中使用。与WinPcap一样&#xff0c;NPCAP库提供了一些API&#xff0c;使开发人员可以轻松地在其应用程序中捕获和处理网络数据…

clickhouse、Doris、Kylin对比

clickhouse ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用C语言编写&#xff0c;是基于 MPP 架构的分布式 ROLAP &#xff08;Relational OLAP&#xff09;分析引擎主要用于在线分析处理查询&#xff08;OLAP&#xff…

【Redis系列】在Centos7上安装Redis5.0保姆级教程!

哈喽&#xff0c; 大家好&#xff0c;我是小浪。那么最近也是在忙秋招&#xff0c;很长一段时间没有更新文章啦&#xff0c;最近呢也是秋招闲下来&#xff0c;当然秋招结果也不是很理想&#xff0c;嗯……这里就不多说啦&#xff0c;回归正题&#xff0c;从今天开始我们就开始正…

SaveFileDialog.OverwritePrompt

SaveFileDialog.OverwritePrompt 获取或设置一个值&#xff0c;该值指示如果用户指定的文件名已存在&#xff0c;Save As 对话框是否显示警告。 public bool OverwritePrompt { get; set; } OverwritePrompt 控制在将要在改写现在文件时是否提示用户 https://vimsky.com/…

Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (三)

这是继之前文章&#xff1a; Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;一&#xff09; Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;二&…

Python必学函数:常用内置函数详解和举例分析

map函数 是根据第一个参数定义的函数&#xff0c;依次作用在序列上&#xff0c;返回一个迭代器 s 1,2,3,4,5 # 将字符串转换成整数列表 list(map(int, s.split(,))) # [1,2,3,4,5]# 求两个连表中元素的和&#xff0c;放入新列表中 data1 [1,2,3] data2 [4,5,6] list(map(l…

Fedora Linux 38下安装音频与视频的解码器和播放器

Fedora Linux 38 操作系统安装好后&#xff0c;默认是没有音频与视频的解码器的&#xff0c;音频与视频的播放体验非常差劲。但是第三方的软件源中有解码器和播放器的软件&#xff0c;需要我们自己手动安装。、 连接互联网&#xff0c;打开Shell命令行&#xff1a; 1. sudo d…

配置Sentinel 控制台

1.遇到的问题 服务网关 | RuoYi 最近调试若依的微服务版本需要用到Sentinel这个组件&#xff0c;若依内部继承了这个组件连上即用。 Sentinel是阿里巴巴开源的限流器熔断器&#xff0c;并且带有可视化操作界面。 在日常开发中&#xff0c;限流功能时常被使用&#xff0c;用…

uni-app配置微信开发者工具

一、配置微信开发者工具路径 工具->设置->运行配置->小程序运行配置->微信开发者工具路径 二、微信开发者工具开启服务端口

YB5302是一款工作于2.7V到6.5V的PFM升压型双节锂电池充电控制集成电路

YB5302 锂电输入升压型双节锂电池充电芯片 概述: YB5302是一款工作于2.7V到6.5V的PFM升压型双节锂电池充电控制集成电路。YB5302采用恒流和准恒压模式(Quasi-CVT™)对电池进行充电管理&#xff0c;内部集成有基准电压源&#xff0c;电感电流检测单元&#xff0c;电池电压检测电…

Remote Local File Inclusion (RFI/LFI)-文件包含漏洞

文件包含是一种功能,在各种开发语言中都提供了内置的文件包含函数。在PHP中,例如,可以使用include()和require()函数来引入另一个文件。这个被引入的文件可以当作PHP代码执行,而忽略其后缀本身。 // if( count( $_GET ) ) if( isset( $file ) )include( $file ); else {he…

易点易动固定资产管理系统:高效盘点海量固定资产的得力助手

固定资产是企业重要的财务资源之一&#xff0c;盘点是保证固定资产准确性和完整性的关键环节。然而&#xff0c;对于拥有海量固定资产的企业来说&#xff0c;传统的手工盘点方式效率低下且容易出错。为了解决这一难题&#xff0c;易点易动固定资产管理系统应运而生。本文将深入…

虹科 | 解决方案 | 非道路移动机械诊断方案

虹科Pico汽车示波器为卡车、拖拉机、叉车、船只、联合收割机、挖掘机开发了专用的测试附件和软件测试菜单&#xff0c;比如 24 V 电池、Bosch Denoxtronic、J1939 通信、发动机和液压传动系统以及部件测试等。我们为从事重型车辆和非道路移动机械的维护与诊断的朋友&#xff0c…

【开题报告】基于微信小程序的旅游攻略分享平台的设计与实现

1.研究背景及意义 旅游已经成为现代人生活中重要的组成部分&#xff0c;人们越来越热衷于探索新的目的地和体验不同的文化。然而&#xff0c;对于旅游者来说&#xff0c;获取准确、可靠的旅游攻略信息并不容易。传统的旅游攻略书籍或网站往往无法提供实时、个性化的建议。因此…

Java New对象分配内存流程

一、流程图 二、流程介绍 1、进行逃逸分析&#xff0c;判断是否能够分配到栈上&#xff1a; Y&#xff1a; 如果能分配到栈上&#xff0c;则进行分配。等方法出栈时&#xff0c;对象内存销毁&#xff0c;可有效减少GC的次数。 N&#xff1a;无法分配到栈上&#xff0c;则判断是…

VMware创建Linux虚拟机之(三)Hadoop安装与配置及搭建集群

Hello&#xff0c;world&#xff01; &#x1f412;本篇博客使用到的工具有&#xff1a;VMware16 &#xff0c;Xftp7 若不熟悉操作命令&#xff0c;推荐使用带GUI页面的CentOS7虚拟机 我将使用带GUI页面的虚拟机演示 虚拟机&#xff08;Virtual Machine&#xff09; 指通过…

对于多分类问题,使用深度学习(Keras)进行迁移学习提升性能

本文是仿照前面的文章,使用Keras迁移学习提升性能,原文是针对二分类问题,使用迁移学习的方式来提升准确率,本文用迁移学习的方式来提升多分类问题的准确率。 同时,在前面的文章中,使用普通的小型3层卷积网络+2层全连接层实现了多分类的85%左右的准确率, 此处将用迁移学…

Mybatisplus 常用注解

一、Mybatisplus 注解 TableName 表名注解&#xff0c;标识实体类对应的表 Documented Retention(RetentionPolicy.RUNTIME) Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) public interface TableName {// 表名String value() default "";// schema…