面试官考我弱引用,强实现之探索Java的WeakHashMap?我...🤡🤡

咦咦咦,各位小可爱,我是你们的好伙伴 bug菌,今天又来给大家手把手教学Java SE系列之集合篇知识点啦,赶紧出来哇,别躲起来啊,听我讲干货记得点点赞,赞多了我就更有动力讲得更欢哦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

js 复制代码
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

前言

在Java开发中,有时我们需要保存大量的对象,但是这些对象又不是一直都需要被内存中持有,比如一些缓存数据或者对象池中的对象等。在这种情况下,使用弱引用(Weak Reference)可以很好地解决这个问题。WeakHashMap作为一种实现了弱引用机制的Map,可以在内存不足的时候自动清理掉不再被引用的对象,从而保证内存使用的高效性和稳定性。本文将对Java中的WeakHashMap进行探究,包括其原理、应用场景、优缺点等。

摘要

本文将详细介绍Java中的WeakHashMap,包括其实现原理、应用场景、优缺点等方面。通过对源代码的解析和应用场景案例的分析,可以更好地理解WeakHashMap的实现机制和使用方法,从而更好地进行Java开发。

WeakHashMap

简介

Java中的WeakHashMap是一种实现了弱引用机制的Map,可以在内存不足的时候自动清理掉不再被引用的对象,从而保证内存使用的高效性和稳定性。它是HashMap的一个变种,在HashMap的基础上增加了弱引用的功能。

众所周知,弱引用是Java中一个比较重要的概念,它可以使得对象在内存不足的情况下被自动回收。在Java中,对象引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。强引用是我们最常用的,只要对象被强引用持有,就不会被垃圾回收机制回收;软引用和弱引用是比较类似的,只是软引用的生命周期比较长,只有在内存不足的情况下才会被回收,而弱引用则是只要在垃圾回收器扫描到这个对象时发现其只被弱引用持有,就会自动将其回收。虚引用则是一种比较特殊的引用,与回收机制相关,本文暂不讨论。

WeakHashMap的数据结构和HashMap类似,但是其键保存的是弱引用类型的键对象,当一个键对象只被弱引用持有时,垃圾回收机制就会回收该键对象,从而在WeakHashMap中删除该键值对。

源代码解析

下面是WeakHashMap的源代码解析,可以看出它的实现十分简单。

java 复制代码
public class WeakHashMap<K, V> extends AbstractMap<K, V>
    implements Map<K, V> {
    
    // 内部维护一个HashMap
    private transient HashMap<K, WeakReference<V>> map;
    
    // 构造函数
    public WeakHashMap(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    // 将指定键映射到此映射中的指定值
    public V put(K key, V value) {
        // 如果键已经被垃圾回收,则将其从Map中移除
        expungeStaleEntries();
        // 键值对存入Map
        return map.put(key, (value == null) ? null : new WeakReference<>(value));
    }
    
    // 返回与指定键相关联的值,如果没有则返回null
    public V get(Object key) {
        // 如果键已经被垃圾回收,则将其从Map中移除
        expungeStaleEntries();
        // 从Map中获取值
        WeakReference<V> wr = map.get(key);
        return (wr == null) ? null : wr.get();
    }
    
    // 移除与此键相关联的映射关系
    public V remove(Object key) {
        // 如果键已经被垃圾回收,则将其从Map中移除
        expungeStaleEntries();
        // 从Map中移除值
        return map.remove(key).get();
    }

    // 移除已被回收的键值对
    private void expungeStaleEntries() {
        // 遍历Map中所有的键值对
        for (Object x; (x = queue.poll()) != null; ) {
            // 移除已经被回收的键值对
            map.remove(((Reference<K>)x).get());
        }
    }
}

从代码中可以看出,WeakHashMap是通过维护一个HashMap和一个队列(queue)来实现键值对的存储。HashMap用于快速查找和存储键值对,队列用于存储已经被回收的键。在put、get、remove操作中,会先调用expungeStaleEntries()方法,该方法会遍历队列中所有已经被回收的键值对,并从HashMap中移除它们。这样可以保证WeakHashMap中不会存在已被回收的键值对。

拓展:

这是一个使用弱引用的实现的Map数据结构,也就是说,如果指向键的弱引用被垃圾回收了,这个键值对也会被自动移除。为了实现这个功能,它内部使用了一个 HashMap 和一个队列。队列中存储了已经被垃圾回收的键的弱引用,每次操作时都会先清理队列中已经被回收的键值对。

在 put 方法中,如果值为 null,那么就直接将键从 Map 中移除,否则将值存入一个 WeakReference 中,再将键值对存入 Map 中。在 get 方法中,从 Map 中获取值,并且将其封装在 WeakReference 中返回。在 remove() 方法中,先使用 get 方法获取值,然后再将键值对从 Map 中移除。

expungeStaleEntries() 方法的作用是移除已经被回收的键值对。它会通过队列中获取已经被回收的键的弱引用,然后将其对应的键值对从 Map 中移除。这样做可以保证 Map 只包含有效的键值对,不会出现因为键被回收而导致的空指针异常。

如下是部分源码截图:

应用场景案例

WeakHashMap的应用场景比较多,主要包括以下几个方面。

缓存

WeakHashMap可以作为缓存的实现方案之一,用于存储已经过期(不再被其他对象引用)的数据。例如,在Android开发中,可以使用WeakReference和WeakHashMap组合实现缓存图片,避免OOM问题。

java 复制代码
public class ImageCache {
    private Map<String, WeakReference<Bitmap>> cache = new WeakHashMap<>();

    public Bitmap get(String url) {
        WeakReference<Bitmap> reference = cache.get(url);
        if (reference != null) {
            return reference.get();
        } else {
            return null;
        }
    }

    public void put(String url, Bitmap bitmap) {
        cache.put(url, new WeakReference<>(bitmap));
    }
}

对象关联

WeakHashMap可以用于存储一些与对象相关的信息,例如,某个对象的状态等。如果该对象不再被引用,则从WeakHashMap中自动移除。这可以避免该对象及其相关信息长时间占用内存。

事件监听

WeakHashMap可以用于实现对象之间的事件监听。例如,在Android开发中,可以使用WeakHashMap来管理Activity之间的事件监听器,避免Activity内存泄漏问题。

优缺点分析

WeakHashMap的优点主要有以下几点。

内存管理

WeakHashMap可以避免由于键值对不再被使用却一直驻留在内存中而造成的内存浪费。

自动维护

WeakHashMap自动维护键值对的生命周期,不需要手动进行管理。

易于扩展

WeakHashMap可以方便地扩展到更复杂的应用场景中。

WeakHashMap的缺点主要有以下几点。

性能

由于WeakHashMap在每次进行put、get、remove等操作时都需要额外进行垃圾回收处理,因此它的性能不及其他Map类高。

易误用

由于WeakHashMap的键值对并不是强引用,因此需要特别注意在某些场景下可能会引起误用问题。

类代码方法介绍

构造函数

java 复制代码
public WeakHashMap(int initialCapacity, float loadFactor)

构造一个具有指定初始容量和负载因子的空WeakHashMap。

put

java 复制代码
public V put(K key, V value)

将指定键映射到此映射中的指定值。键和值都可以为null。返回value。

get

java 复制代码
public V get(Object key)

返回与指定键相关联的值,如果没有则返回null。

remove

java 复制代码
public V remove(Object key)

移除与此键相关联的映射关系。返回移除的值。

size

java 复制代码
public int size()

返回此映射中键值对的数量。

clear

java 复制代码
public void clear()

从此映射中移除所有键值对。

测试用例

测试代码演示

java 复制代码
package com.demo.javase.day65_2;

import java.util.WeakHashMap;

/**
 * WeakHashMap示例演示
 *
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2023-11-06 16:53
 */
public class WeakHashMapTest {

    public static void main(String[] args) {

        WeakHashMap<String, Object> map = new WeakHashMap<>();
        Object value = new Object();
        map.put("key", value);
        System.out.println(map.containsValue(value)); // true
        value = null;
        System.gc();
        // 等待垃圾回收
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(map.containsValue(value)); // false
    }
}

测试结果

根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

测试代码中,先将一个值放入WeakHashMap中,然后将值的引用置为null,并执行System.gc()方法进行垃圾回收,最后判断WeakHashMap中是否还包含该值。

测试结果为false,说明值已经被垃圾回收器回收了。

测试代码分析

根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

这是一个使用WeakHashMap的示例程序。在main方法中,首先创建了一个WeakHashMap对象,并向其中添加一个键值对,键为"key",值为一个Object对象。然后输出map中是否包含该Object的值,应当会输出true。

接着将value设置为null,并调用System.gc()进行垃圾回收。注意,WeakHashMap中的键值对只有在该键不再被任何强引用持有的时候,才会被自动删除。因此,当我们将value设置为null时,该Object对象就没有了强引用。但是,由于WeakHashMap中还对该Object对象有一个弱引用,因此它暂时不会被回收。

接下来通过Thread.sleep()方法等待一段时间,确保垃圾回收已完成。然后再次输出map中是否包含该Object的值,此时应会输出false。这是因为在垃圾回收后,WeakHashMap中对该Object对象的弱引用已经被回收,所以该键值对也已经被从map中删除了。

总结

在此,我们来做个小小的总结,本文深入探讨了Java中的WeakHashMap,这是一种特殊的HashMap实现,它使用弱引用作为键,允许在内存不足时自动回收不再被引用的键值对。文章首先介绍了WeakHashMap的基本概念和它在Java中的作用,然后通过源代码解析详细说明了WeakHashMap的工作原理,包括它的构造函数、put、get、remove等方法的实现细节。

文章还提供了WeakHashMap的几个典型应用场景,如缓存实现、对象关联和事件监听,并分析了使用WeakHashMap的优点,例如内存管理的高效性和自动维护的特性,同时也指出了它的性能和易误用性等缺点。

最后,我通过一个实际的测试用例演示了如何使用WeakHashMap,并解释了测试结果。通过这个测试用例,同学们可以清晰地看到当WeakHashMap中的键失去了所有强引用后,与之关联的值是如何被垃圾回收器回收的。

总的来说,我给大家提供了一个全面的WeakHashMap指南,从理论到实践,帮助同学们理解并有效地使用这一并发集合类型。通过阅读本文,Java开发者可以更好地管理内存,优化应用性能,并避免内存泄漏等问题。

... ...

好啦,这期的内容就基本接近尾声啦,若你想学习更多,你可以看看专栏的导读篇《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。功不唐捐,久久为功!

「赠人玫瑰,手留余香」,咱们下期拜拜~~

附录源码

如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你

无论你是计算机专业的学生,还是对编程感兴趣的跨专业小白,都建议直接入手「滚雪球学Java」专栏;该专栏不仅免费,bug菌还郑重承诺,只要你学习此专栏,均能入门并理解Java SE,以全网最快速掌握Java语言,每章节源码均同步「Gitee」,你真值得拥有;学习就像滚雪球一样,越滚越大,带你指数级提升。

码字不易,如果这篇文章对你有所帮助,帮忙给bugj菌来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。

📣关于我

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 20w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!


相关推荐
hsjcjh8 小时前
【MySQL】C# 连接MySQL
java
敖正炀8 小时前
LinkedBlockingDeque详解
java
wangyadong3178 小时前
datagrip 链接mysql 报错
java
untE EADO8 小时前
Tomcat的server.xml配置详解
xml·java·tomcat
ictI CABL9 小时前
Tomcat 乱码问题彻底解决
java·tomcat
敖正炀9 小时前
DelayQueue 详解
java
uzong9 小时前
最新:阿里正式发布首款AI开发工具Meoo(秒悟),0门槛、一键部署上线
人工智能·后端
用户8356290780519 小时前
Python 操作 PowerPoint:添加与设置文本框完整教程
后端·python
HuaidongLi9 小时前
三级缓存与循环依赖
后端
tongxh4239 小时前
Spring Boot 3.X:Unable to connect to Redis错误记录
spring boot·redis·后端