Java 集合:WeakHashMap 类

WeakHashMap 是 Java 集合框架中一种特殊的 Map 实现,它与普通 HashMap 的主要区别在于键的引用方式 —— WeakHashMap 的键使用弱引用(WeakReference),当键不再再被其他强引用指向时,可能会被垃圾回收器回收,从而自动从 Map 中移除对应的键值对。这种特性使它适合存储临时缓存数据。

  

WeakHashMap 主要特性

(1)弱引用键机制:键被包装在弱引用中,当键对象没有其他强引用时,会被标记为可回收。当垃圾回收发生时,被回收的键对应的键值对会从 WeakHashMap 中自动移除(无需手动删除)。注意,如果值仍被强引用,即使键被回收,值不会被回收。

(2)存储结构:底层与 HashMap 类似,采用哈希表(数组 + 链表 / 红黑树)实现。还额外维护一个引用队列(ReferenceQueue),用于跟踪被回收的键,以便清理对应的键值对。

(3)键值约束:键可以为 null(null 作为键时,其引用特性与普通 null 一致)。值也可以为 null。

(4)非线程安全:本身不是线程安全的,多线程环境下需手动同步,如使用 Collections.synchronizedMap()方法进行包装。

(5)迭代行为:迭代过程中可能出现元素被移除(因垃圾回收),因此迭代器是「弱一致的」(weakly consistent),不会抛出 ConcurrentModificationException异常,这也导致迭代器可能不会反映 Map 的最新状态。

  

Java 引用类型

在 Java 中,引用类型分为四种,它们的主要区别在于垃圾回收机制对对象的处理方式不同,从强到弱依次为:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。

强引用(Strong Reference)

最常见的引用类型,如Object obj = new Object(),obj就是强引用。

只要对象被强引用关联,垃圾回收器就不会回收该对象,即使内存不足,JVM 也会抛出OutOfMemoryError,而不会回收强引用指向的对象。只有当强引用被显式置为null(如obj = null),且对象没有其他强引用时,才可能被回收。

强引用是日常开发中默认使用的引用方式,用于确保对象在使用期间不被回收。

软引用(Soft Reference)

通过java.lang.ref.SoftReference类实现,如SoftReference<Object> softRef = new SoftReference<>(new Object())。

软引用关联的对象,在内存充足时不会被回收,但当内存不足(即将发生 OOM)时,会被垃圾回收器回收。

软引用适合缓存场景(如图片缓存),当内存紧张时释放缓存,避免 OOM。

注意:回收软引用对象时,会同时回收其关联的对象,但软引用本身需要配合引用队列(ReferenceQueue)手动清理,避免内存泄漏。

弱引用(Weak Reference)

通过java.lang.ref.WeakReference类实现,如WeakReference<Object> weakRef = new WeakReference<>(new Object())。

弱引用关联的对象,只要发生垃圾回收,无论内存是否充足,都会被回收。

弱引用非常适合存储非必需对象,且希望对象随垃圾回收自动释放(如 ThreadLocal 中的 Entry、WeakHashMap 的 key)。

注意:弱引用的生命周期比软引用更短,同样可配合引用队列处理回收后的清理逻辑。

虚引用(Phantom Reference)

通过java.lang.ref.PhantomReference类实现,必须配合引用队列使用,如PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue)。

虚引用对对象的生命周期没有影响,无法通过虚引用获取对象(get()方法始终返回null)。

当对象被回收时,虚引用会被加入关联的引用队列,用于跟踪对象的回收状态。

虚引用主要用于监听对象的垃圾回收过程(如释放堆外内存资源),是唯一能在对象被回收前收到通知的引用类型。

  

WeakHashMap 构造方法

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 常用方法

WeakHashMap 实现了 Map 接口的所有方法,核心方法与普通 Map 类似:

  • V put(K key, V value)

向 Map 中添加键值对。键(key)会被包装为弱引用(WeakReference)存储,而值(value)通过强引用持有。如果键为 null,会被特殊处理(视为一个独立的弱引用键,null 键的回收逻辑与普通键一致)。执行成功,返回该键之前关联的值,如果键不存在则返回 null。

注意:如果值对象强引用其对应的键(直接或间接),会导致键无法被回收,从而使键值对永久保留(可能造成内存泄漏)。

  • void putAll(Map<? extends K, ? extends V> m)

批量添加另一个 Map(m)中的所有键值对到当前 WeakHashMap。WeakHashMap 会对 m 中的每个键值对调用 put 方法,因此键同样会被包装为弱引用,值以强引用存储。

注意:如果 m 中的键在添加后没有外部强引用,后续可能被垃圾回收,导致对应键值对被自动移除。

  • V get(Object key)

根据键获取对应的值。查找时会先检查键是否已被回收(即弱引用是否失效)。如果键已被回收,会自动移除对应的键值对,并返回 null。

如果键未被回收,则返回其关联的值;如果键不存在,也返回 null。

注意:无法通过返回值 null 区分 “键不存在” 和 “键已被回收”,需结合 containsKey 方法判断。

  • Set<K> keySet()

返回所有键的 Set 视图。该集合是 “动态视图”,包含当前未被回收的键,但可能残留已被回收的键(尚未被清理)。

当访问该集合(如迭代、调用 size 等)时,会触发一次 “清理操作”,自动移除所有已被回收的键,因此集合内容可能随垃圾回收和访问行为动态变化。

注意:迭代过程中如果键被回收,可能导致迭代元素数量减少(符合快速失败机制,若结构被修改会抛出 ConcurrentModificationException)。

  • Collection<V> values()

返回所有值的 Collection 视图。与 keySet 类似,是动态视图,包含当前有效键关联的值。访问时会清理已回收键对应的 值,因此集合内容可能动态减少。

  • Set<Map.Entry<K, V>> entrySet()

返回所有键值对(Entry)的 Set 视图。每个 Entry 中的键是弱引用关联的对象,值是强引用。访问该集合时会清理已回收键对应的 Entry,因此迭代过程中 Entry 数量可能减少。

注意:Entry 的 getKey() 方法可能返回 null(若键已被回收但尚未清理),但清理后会自动移除这类 Entry。

  • V remove(Object key)

手动移除指定键对应的键值对。返回被移除的值(若键不存在或已被回收,返回 null)。

此方法是主动删除,无论键是否被回收,都会移除对应的键值对;而自动回收是被动触发(键被垃圾回收后)。

  • void clear()

清空 Map 中所有键值对,包括未被回收的键。调用后 size() 会返回 0,所有弱引用键被清除,值也随之释放(若值无其他引用)。

  • boolean containsKey(Object key)

判断 Map 中是否包含指定键(且键未被回收)。检查时会先清理已回收的键,再判断键是否存在。

若键已被回收,返回 false;若键存在且未被回收,返回 true。

  • boolean containsValue(Object value)

判断 Map 中是否包含指定值。遍历所有未被回收的键对应的值,检查是否存在与 value 相等(equals 方法)的值。

注意:若值被多个键共享,即使部分键被回收,只要有一个键未被回收且关联该值,就返回 true。

  • int size()

返回当前有效键值对的数量(即键未被回收的键值对)。调用时会先清理已回收的键,再统计剩余数量,因此返回值可能随垃圾回收而减少(即使未手动修改映射)。

  • boolean isEmpty()

判断映射是否为空(即有效键值对数量为 0)。内部调用 size() == 0,因此同样会先清理已回收的键,返回结果反映当前状态(可能前一次调用返回 false,后一次因键被回收返回 true)。

  

WeakHashMap 简单示例

下面通过一个简单示例介绍 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 适用场景

下面介绍几个 WeakHashMap 使用场景:

(1) 缓存场景:存储临时缓存数据,当数据不再被使用(无强引用)时自动释放内存,避免内存泄漏。例如:缓存图片资源、数据库查询结果等。

(2)元数据关联:为对象动态关联附加信息,当对象被回收时,元数据自动清理。例如:为临时创建的对象添加额外属性。

(3)资源自动释放:需要随目标对象生命周期自动管理的资源映射,如监听器注册(监听器随目标对象回收而移除)。

注意:WeakHashMap 不适合存储需要长期保留的键值对,且其 size() 方法返回的结果可能不精确(因为垃圾回收可能在任意时刻发生)。在使用时,应避免依赖其元素的存在性,除非能确保键有强引用。

更多信息参考 https://docs.oracle.com/javase/8/docs/api/java/util/WeakHashMap.html API 文档。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号