WeakHashMap 是 Java 集合框架中一种特殊的 Map 实现,它与普通 HashMap 的主要区别在于键的引用方式 —— WeakHashMap 的键使用弱引用(WeakReference),当键不再再被其他强引用指向时,可能会被垃圾回收器回收,从而自动从 Map 中移除对应的键值对。这种特性使它适合存储临时缓存数据。
(1)弱引用键机制:键被包装在弱引用中,当键对象没有其他强引用时,会被标记为可回收。当垃圾回收发生时,被回收的键对应的键值对会从 WeakHashMap 中自动移除(无需手动删除)。注意,如果值仍被强引用,即使键被回收,值不会被回收。
(2)存储结构:底层与 HashMap 类似,采用哈希表(数组 + 链表 / 红黑树)实现。还额外维护一个引用队列(ReferenceQueue),用于跟踪被回收的键,以便清理对应的键值对。
(3)键值约束:键可以为 null(null 作为键时,其引用特性与普通 null 一致)。值也可以为 null。
(4)非线程安全:本身不是线程安全的,多线程环境下需手动同步,如使用 Collections.synchronizedMap()方法进行包装。
(5)迭代行为:迭代过程中可能出现元素被移除(因垃圾回收),因此迭代器是「弱一致的」(weakly consistent),不会抛出 ConcurrentModificationException异常,这也导致迭代器可能不会反映 Map 的最新状态。
在 Java 中,引用类型分为四种,它们的主要区别在于垃圾回收机制对对象的处理方式不同,从强到弱依次为:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。
最常见的引用类型,如Object obj = new Object(),obj就是强引用。
只要对象被强引用关联,垃圾回收器就不会回收该对象,即使内存不足,JVM 也会抛出OutOfMemoryError,而不会回收强引用指向的对象。只有当强引用被显式置为null(如obj = null),且对象没有其他强引用时,才可能被回收。
强引用是日常开发中默认使用的引用方式,用于确保对象在使用期间不被回收。
通过java.lang.ref.SoftReference类实现,如SoftReference<Object> softRef = new SoftReference<>(new Object())。
软引用关联的对象,在内存充足时不会被回收,但当内存不足(即将发生 OOM)时,会被垃圾回收器回收。
软引用适合缓存场景(如图片缓存),当内存紧张时释放缓存,避免 OOM。
注意:回收软引用对象时,会同时回收其关联的对象,但软引用本身需要配合引用队列(ReferenceQueue)手动清理,避免内存泄漏。
通过java.lang.ref.WeakReference类实现,如WeakReference<Object> weakRef = new WeakReference<>(new Object())。
弱引用关联的对象,只要发生垃圾回收,无论内存是否充足,都会被回收。
弱引用非常适合存储非必需对象,且希望对象随垃圾回收自动释放(如 ThreadLocal 中的 Entry、WeakHashMap 的 key)。
注意:弱引用的生命周期比软引用更短,同样可配合引用队列处理回收后的清理逻辑。
通过java.lang.ref.PhantomReference类实现,必须配合引用队列使用,如PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue)。
虚引用对对象的生命周期没有影响,无法通过虚引用获取对象(get()方法始终返回null)。
当对象被回收时,虚引用会被加入关联的引用队列,用于跟踪对象的回收状态。
虚引用主要用于监听对象的垃圾回收过程(如释放堆外内存资源),是唯一能在对象被回收前收到通知的引用类型。
WeakHashMap 提供了 4 种构造方法:
WeakHashMap() 构造一个新的、空的 WeakHashMap,使用默认的初始容量(16)和加载因子(0.75)。
WeakHashMap(int initialCapacity) 构造一个新的、空的 WeakHashMap,使用指定的初始容量和默认的加载因子(0.75)。
WeakHashMap(int initialCapacity, float loadFactor) 构造一个新的、空的 WeakHashMap,使用指定的初始容量和指定的加载因子。
WeakHashMap(Map<? extends K,? extends V> m) 构造一个新的 WeakHashMap,包含与指定映射相同的映射关系。注意,键会被包装为弱引用。
WeakHashMap 实现了 Map 接口的所有方法,核心方法与普通 Map 类似:
向 Map 中添加键值对。键(key)会被包装为弱引用(WeakReference)存储,而值(value)通过强引用持有。如果键为 null,会被特殊处理(视为一个独立的弱引用键,null 键的回收逻辑与普通键一致)。执行成功,返回该键之前关联的值,如果键不存在则返回 null。
注意:如果值对象强引用其对应的键(直接或间接),会导致键无法被回收,从而使键值对永久保留(可能造成内存泄漏)。
批量添加另一个 Map(m)中的所有键值对到当前 WeakHashMap。WeakHashMap 会对 m 中的每个键值对调用 put 方法,因此键同样会被包装为弱引用,值以强引用存储。
注意:如果 m 中的键在添加后没有外部强引用,后续可能被垃圾回收,导致对应键值对被自动移除。
根据键获取对应的值。查找时会先检查键是否已被回收(即弱引用是否失效)。如果键已被回收,会自动移除对应的键值对,并返回 null。
如果键未被回收,则返回其关联的值;如果键不存在,也返回 null。
注意:无法通过返回值 null 区分 “键不存在” 和 “键已被回收”,需结合 containsKey 方法判断。
返回所有键的 Set 视图。该集合是 “动态视图”,包含当前未被回收的键,但可能残留已被回收的键(尚未被清理)。
当访问该集合(如迭代、调用 size 等)时,会触发一次 “清理操作”,自动移除所有已被回收的键,因此集合内容可能随垃圾回收和访问行为动态变化。
注意:迭代过程中如果键被回收,可能导致迭代元素数量减少(符合快速失败机制,若结构被修改会抛出 ConcurrentModificationException)。
返回所有值的 Collection 视图。与 keySet 类似,是动态视图,包含当前有效键关联的值。访问时会清理已回收键对应的 值,因此集合内容可能动态减少。
返回所有键值对(Entry)的 Set 视图。每个 Entry 中的键是弱引用关联的对象,值是强引用。访问该集合时会清理已回收键对应的 Entry,因此迭代过程中 Entry 数量可能减少。
注意:Entry 的 getKey() 方法可能返回 null(若键已被回收但尚未清理),但清理后会自动移除这类 Entry。
手动移除指定键对应的键值对。返回被移除的值(若键不存在或已被回收,返回 null)。
此方法是主动删除,无论键是否被回收,都会移除对应的键值对;而自动回收是被动触发(键被垃圾回收后)。
清空 Map 中所有键值对,包括未被回收的键。调用后 size() 会返回 0,所有弱引用键被清除,值也随之释放(若值无其他引用)。
判断 Map 中是否包含指定键(且键未被回收)。检查时会先清理已回收的键,再判断键是否存在。
若键已被回收,返回 false;若键存在且未被回收,返回 true。
判断 Map 中是否包含指定值。遍历所有未被回收的键对应的值,检查是否存在与 value 相等(equals 方法)的值。
注意:若值被多个键共享,即使部分键被回收,只要有一个键未被回收且关联该值,就返回 true。
返回当前有效键值对的数量(即键未被回收的键值对)。调用时会先清理已回收的键,再统计剩余数量,因此返回值可能随垃圾回收而减少(即使未手动修改映射)。
判断映射是否为空(即有效键值对数量为 0)。内部调用 size() == 0,因此同样会先清理已回收的键,返回结果反映当前状态(可能前一次调用返回 false,后一次因键被回收返回 true)。
下面通过一个简单示例介绍 WeakHashMap 的简单用法:
package com.hxstrive.java_collection.weakHashMapExample;
import java.util.WeakHashMap;
public class WeakHashMapExample {
public static void main(String[] args) {
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object strongKey = new Object(); // 强引用的键
Object weakKey = new Object(); // 仅被WeakHashMap引用的键
map.put(strongKey, "强引用键对应的值");
map.put(weakKey, "弱引用键对应的值");
System.out.println("初始大小:" + map.size()); // 输出:2
// 移除weakKey的强引用(此时仅WeakHashMap持有其弱引用)
weakKey = null;
// 提示垃圾回收
System.gc();
// 给垃圾回收一些时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 弱引用键已被回收,对应键值对被移除
System.out.println("GC后大小:" + map.size()); // 输出:1
System.out.println("强引用键的值:" + map.get(strongKey)); // 仍存在
System.out.println("弱引用键的值:" + map.get(new Object())); // 已回收,返回null
}
}输出结果(可能因 JVM 垃圾回收时机略有差异):
初始大小:2 GC后大小:1 强引用键的值:强引用键对应的值 弱引用键的值:null
下面介绍几个 WeakHashMap 使用场景:
(1) 缓存场景:存储临时缓存数据,当数据不再被使用(无强引用)时自动释放内存,避免内存泄漏。例如:缓存图片资源、数据库查询结果等。
(2)元数据关联:为对象动态关联附加信息,当对象被回收时,元数据自动清理。例如:为临时创建的对象添加额外属性。
(3)资源自动释放:需要随目标对象生命周期自动管理的资源映射,如监听器注册(监听器随目标对象回收而移除)。
注意:WeakHashMap 不适合存储需要长期保留的键值对,且其 size() 方法返回的结果可能不精确(因为垃圾回收可能在任意时刻发生)。在使用时,应避免依赖其元素的存在性,除非能确保键有强引用。
更多信息参考 https://docs.oracle.com/javase/8/docs/api/java/util/WeakHashMap.html API 文档。