Java中的引用类型(强引用、软引用、弱引用、虚引用)介绍,示例WeakHashMap的使用【享学Java】(下)

简介: Java中的引用类型(强引用、软引用、弱引用、虚引用)介绍,示例WeakHashMap的使用【享学Java】(下)

使用WeakHashMap改进:


WeakHashMap类在java.util包内,它实现了Map接口,是HashMap的一种实现(但不是它的子类),它使用弱引用作为内部数据的存储方案。WeakHashMap是弱引用的一种典型应用,它可以作为简单的缓存表解决方案。


// @since 1.2  JDK1.2出来的
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
  // 内部自己new了一个ReferenceQueue
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
  ...
  // 它使用若引用的核心主要是Entry这个内类
  // 继承自WeakReference,这样一来,整个Entry就是一个WeakReferenc
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        Entry(Object key, V value,ReferenceQueue<Object> queue,int hash, Entry<K,V> next) {
            super(key, queue); // 调用WeakReference的构造函数
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
    }
  ...
}


使用WeakHashMap改造如下(只改一行代码):


public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 只改变了这一句话而已~
        Map<String, Object> map = new WeakHashMap<>();
        for (int i = 0; i < 10000; i++) {
            map.put("key" + i, new byte[i]);
        }
        System.out.println(map.size());
    }
}


输出:


6(备注:这个值可能是10、20、100等等)


从结果中可以看出Map的size()长度不是10000了,也就是说出现了数据丢失~,但运行不会报OOM错误了。

由此可见,WeakHashMap会在系统内存紧张时使用弱引用,自动释放掉持有弱引用的内存数据。

说明:如果WeakHashMap的key都在系统内持有强引用,那么WeakHashMap就退化为普通的HashMap,因为所有的项都无法被自动清理。所以尽量不要使你的WeakHashMap里的key被强引用了,否则它将失去效果~~~


当然我们也可以自定义一个基于WeakReference或者SoftReference的缓存结构,有兴趣自定义的可以参考这个示例:java中SoftReference与WeakReference应用于高速缓存示例

为什么是弱键而不是弱值?


这个问题你应该思考。WeakHashMap它就不是为缓存而设计的,因为它是弱键而不是弱值,而一般而言值才是最占用内存空间的,所以它天生就不太适合当作缓存来用(当然当缓存来用效果比HashMap肯定还是好点的)。


WeakHashMap的经典使用场景是:保存您不控制生命周期的对象的元数据

比如用于数据跟踪,一个简单的例子(和我以前用过的)可能是这样的:WeakHashMap<Thread, SomeMetaData>,可以跟踪你的系统中的各种线程在做什么,当线程死亡时,该条目将从您的映射中静默移除。所以你遍历就可以拿出活动线程的元数据喽。


作为缓存而言,我们是希望有个类似Map<K,SoftReference<V>>(弱值)的存储结构,那么接下来我就自己给出一个例子,可以给与你作为高速缓存来使用:

public class SoftReferenceCache<K, V> {
    private static final int DEFAULT_MAP_SIZE = 1024; // 初始容量
    private Map<K, CacheReference<K, V>> softReferenceMap = new HashMap<>(DEFAULT_MAP_SIZE);
    private final ReferenceQueue<V> referenceQueue; // 方便从缓存池中清除失效的软引用
    private final ValueNotFoundProvider<K, V> objectNotFoundProvider; // 当key找不到的时候的处理函数
    // 若ObjectNotFoundHandler用户不指定,会采用默认的:啥都不做
    public SoftReferenceCache() {
        this(null);
    }
    public SoftReferenceCache(ValueNotFoundProvider<K, V> handler) {
        this.referenceQueue = new ReferenceQueue<>();
        this.objectNotFoundProvider = handler != null ? handler : (k) -> null;
    }
    public V put(K key, V value) {
        // 清楚无效垃圾(也会从softReferenceMap里移除)
        clearInvalidReference();
        CacheReference<K, V> v = softReferenceMap.put(key, new CacheReference<>(key, value, referenceQueue));
        if (v == null) {
            return null;
        }
        return v.get();
    }
    /**
     * 获取,若没获取到,会使用objectNotFoundProvider提供值
     */
    public V get(K key) {
        V value = null;
        CacheReference<K, V> ref = softReferenceMap.get(key);
        if (ref == null || (ref != null && ref.get() == null)) {
            //软引用指向的对象被回收,并缓存该软引用
            value = this.objectNotFoundProvider.provideValue(key);
            if (value != null) {
                put(key, value);
            }
        }
        clearInvalidReference(); // 此时调用清理
        return value;
    }
    public boolean containsKey(K key) {
        clearInvalidReference();
        return softReferenceMap.containsKey(key);
    }
    public int size() {
        clearInvalidReference();
        return softReferenceMap.size();
    }
    public boolean isEmpty() {
        return size() == 0;
    }
    public void clear() {
        clearInvalidReference();
        softReferenceMap.clear();
    }
    public V remove(K key) {
        clearInvalidReference();
        CacheReference<K, V> v = softReferenceMap.remove(key);
        if (v == null) {
            return null;
        }
        return v.get();
    }
    /**
     * 根据已经被回收的value对应的key,是为了清除掉map里的键值对,防止本Cache的内存泄漏
     */
    private void clearInvalidReference() {
        CacheReference<K, V> cacheReference;
        while ((cacheReference = (CacheReference<K, V>) referenceQueue.poll()) != null) {
            softReferenceMap.remove(cacheReference.getKey());
        }
    }
    /**
     * 缓存应用类,继承软引用
     * 这里把key也保存起来,是因为清理的时候也得清理键值对,否则Map可能会内存泄漏的
     */
    private static class CacheReference<K, V> extends SoftReference<V> {
        private final K key;
        public CacheReference(K key, V reference, ReferenceQueue<V> queue) {
            super(reference, queue);
            this.key = key;
        }
        public K getKey() {
            return this.key;
        }
    }
    /**
     * 若缓存里木有,就交给使用者去获取(比如查库~~~)
     */
    @FunctionalInterface
    public interface ValueNotFoundProvider<K, V> {
        V provideValue(K k);
    }
}


总结


其实软引用、弱引用在Android这种移动端应用,对内存更敏感的应用中使用更多些,服务端毕竟对内存敏感度差点,所以没受到很多人的重视。但是这一块个人觉得在设计高效的框架时,还是可以使用的~

Tips:谷歌不推荐使用软引用SoftReference,而建议使用弱引用WeakReference(但是我觉得通用的缓存设计用软引用,或许还更好些吧)


image.png

相关文章
|
2月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
80 1
|
3月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
56 1
|
2月前
|
Java
在Java中实现接口的具体代码示例
可以根据具体的需求,创建更多的类来实现这个接口,以满足不同形状的计算需求。希望这个示例对你理解在 Java 中如何实现接口有所帮助。
92 38
|
3月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
95 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
3月前
|
存储 Java
什么是带有示例的 Java 中的交错数组?
什么是带有示例的 Java 中的交错数组?
61 9
|
3月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
33 1
|
3月前
|
存储 Java 程序员
【一步一步了解Java系列】:何为数组,何为引用类型
【一步一步了解Java系列】:何为数组,何为引用类型
38 1
|
3月前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
31 0
|
6月前
|
缓存 Java 程序员
Java面试题:解释强引用、软引用、弱引用和虚引用在Java中是如何工作的?
Java面试题:解释强引用、软引用、弱引用和虚引用在Java中是如何工作的?
43 1
|
8月前
|
Java
【JVM】深入理解Java引用类型:强引用、软引用、弱引用和虚引用
【JVM】深入理解Java引用类型:强引用、软引用、弱引用和虚引用
536 0