Google Guava 缓存工具使用详解

 更新时间:2023年12月22日 11:24:39   作者:骑个小蜗牛  
这篇文章主要介绍了Guava自加载缓存LoadingCache使用指南,通过这些内容介绍,了解了LoadingCache的基本原理和用法,包括如何创建和配置缓存,以及如何结合Java 8的特性来优化代码,需要的朋友可以参考下

Google Guava 缓存工具使用详解

缓存工具

Guava提供了Cache接口和相关的类来支持缓存功能,它提供了高性能、线程安全的内存缓存,可以用于优化应用程序的性能。

特点:

  • 自动回收过期数据
  • 支持缓存项的定时回收
  • 支持缓存项的最大数量限制
  • 支持缓存项的大小限制
  • 支持缓存项的权重限制,简化开发者实现基于容量的限制
  • 提供了统计信息

相关接口类:

接口/类描述
Cache<K, V>接口表示一种能够存储键值对的缓存结构
LoadingCache<K, V>是 Cache 接口的子接口,用于在缓存中自动加载缓存项
CacheLoader<K, V>在使用 LoadingCache 时提供加载缓存项的逻辑
CacheBuilder用于创建 CacheLoadingCache 实例的构建器类
CacheStats用于表示缓存的统计信息,如命中次数、命中率、加载次数、存储次数等
RemovalListener<K, V>用于监听缓存条目被移除的事件,并在条目被移除时执行相应的操作

使用示例:

    public static void main(String[] args) throws Exception {
        // 创建Cache实例
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .initialCapacity(2)                                         // 设置初始容量
                .concurrencyLevel(4)                                        // 设置并发级别
                .maximumSize(5)                                             // 设置最大容量
//                .maximumWeight(1000)                                        // 设置最大权重
//                .weigher((Weigher<String, String>) (k, v) -> v.length())    // 设置权重计算器
                .expireAfterWrite(Duration.ofSeconds(3))                    // 写入后3秒过期
                .expireAfterAccess(Duration.ofSeconds(20))                  // 访问后20秒过期
                .refreshAfterWrite(Duration.ofSeconds(10))                  // 写入后自动刷新,3秒刷新一次
                .recordStats()                                              // 开启统计信息记录
                .removalListener(notification -> {                          // 设置移除监听
                    // 缓存Key被移除时触发
                    String cause = "";
                    if (RemovalCause.EXPLICIT.equals(notification.getCause())) {
                        cause = "被显式移除";
                    } else if (RemovalCause.REPLACED.equals(notification.getCause())) {
                        cause = "被替换";
                    } else if (RemovalCause.EXPIRED.equals(notification.getCause())) {
                        cause = "被过期移除";
                    } else if (RemovalCause.SIZE.equals(notification.getCause())) {
                        cause = "被缓存条数超上限移除";
                    } else if (RemovalCause.COLLECTED.equals(notification.getCause())) {
                        cause = "被垃圾回收移除";
                    }
                    System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + notification.getKey() + " 移除了, 移除原因: " + cause);
                })
                .build(new CacheLoader<String, String>() {                  // 设置缓存重新加载逻辑
                    @Override
                    public String load(String key) {
                        // 重新加载指定Key的值
                        String newValue = "value" + (int)Math.random()*100;
                        System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + key + " 重新加载,新value:" + newValue);
                        return newValue;
                    }
                });
        // 将数据放入缓存
        cache.put("key0", "value0");
        cache.invalidate("key0");
        cache.put("key1", "value1");
        cache.put("key1", "value11");
        cache.put("key2", "value22");
        cache.put("key3", "value3");
        cache.put("key4", "value4");
        cache.put("key5", "value5");
        cache.put("key6", "value6");
        cache.put("key7", "value7");
        cache.put("key8", "value8");
        while (true) {
            // 获取数据
            System.out.println(DateUtil.formatDateTime(new Date()) + " get key1 value: " + cache.get("key1"));
            // 统计信息
            System.out.println(DateUtil.formatDateTime(new Date()) + " get stats: " + cache.stats());
            Thread.sleep(1000);
        }
    }

打印日志:

2023-11-24 15:48:17 Key: key0 移除了, 移除原因: 被显式移除
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被替换
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key2 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key3 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key1 重新加载,新value:value0
2023-11-24 15:48:17 Key: key4 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 get key1 value: value0
2023-11-24 15:48:17 get stats: CacheStats{hitCount=0, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:18 get key1 value: value0
2023-11-24 15:48:18 get stats: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:19 get key1 value: value0
2023-11-24 15:48:19 get stats: CacheStats{hitCount=2, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:20 Key: key5 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key6 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key7 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key8 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key1 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key1 重新加载,新value:value0
2023-11-24 15:48:20 get key1 value: value0
2023-11-24 15:48:20 get stats: CacheStats{hitCount=2, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:21 get key1 value: value0
2023-11-24 15:48:21 get stats: CacheStats{hitCount=3, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:22 get key1 value: value0
2023-11-24 15:48:22 get stats: CacheStats{hitCount=4, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:23 Key: key1 移除了, 移除原因: 被过期移除
2023-11-24 15:48:23 Key: key1 重新加载,新value:value0
2023-11-24 15:48:23 get key1 value: value0
2023-11-24 15:48:23 get stats: CacheStats{hitCount=4, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:24 get key1 value: value0
2023-11-24 15:48:24 get stats: CacheStats{hitCount=5, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:25 get key1 value: value0
2023-11-24 15:48:25 get stats: CacheStats{hitCount=6, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
......

Cache接口

Cache接口是Guava缓存的核心接口,定义了缓存的基本操作。

它是使用 CacheBuilder 创建的。它提供了基本的缓存操作,如 put、get、delete等方法。同时,Cache 还提供了诸如统计信息、缓存项的值获取方式、缓存项的失效、缓存项的回收等方法,可以满足大多数应用的需求。

主要方法:

方法描述
V get(K key, Callable<? extends V> valueLoader)根据键获取对应的缓存值,如果缓存中不存在该键,会使用 valueLoader 加载并存储该值。
V getIfPresent(K key)根据键获取对应的缓存值,如果不存在则返回 null。
Map<K, V> getAllPresent(Iterable<?> keys)获取多个键对应的缓存值的映射,如果缓存中不存在某个键,则该键不会出现在返回的映射中。
void put(K key, V value)将键值对放入缓存中。如果键已经存在,则会替换对应的值。
void putAll(Map<? extends K, ? extends V> map)将多个键值对添加到缓存中。
void invalidate(Object key)根据键从缓存中移除条目。
void invalidateAll(Iterable<?> keys)根据键集合移除多个条目。
void invalidateAll()移除缓存中的所有条目。
long size()返回缓存中的条目数。
CacheStats stats()返回缓存的统计信息。
ConcurrentMap<K, V> asMap()返回缓存的并发映射视图。
void cleanUp()执行缓存的清理操作。

LoadingCache接口

LoadingCache 继承自 Cache 接口,它是一个带有自动加载功能的缓存接口。

在使用 LoadingCache 时,如果缓存中不存在所需的键值对,则会自动调用CacheLoader的加载方法进行加载,并将加载的结果存入缓存中。

主要方法:

方法说明
get(K key)根据指定的键检索值,如果键不存在,将调用 CacheLoader 进行加载并返回对应的值
getAll(Iterable<? extends K> keys)根据给定的键集合批量检索值,并返回一个 Map 对象,对于已缓存的键将直接返回对应的值,对于未缓存的键将通过 CacheLoader 进行加载。
getUnchecked(K key)获取指定键对应的值,如果缓存中不存在该键,则返回 null,不会触发CacheLoader 加载。
refresh(K key)刷新指定键对应的值,即使用 CacheLoader 重新加载该键对应的值,并更新缓存。

CacheBuilder类

CacheBuilder类是用于创建Guava缓存的构建器。可以使用该类的newBuilder()方法创建一个构建器实例,并通过一系列方法设置缓存的属性,例如最大容量、过期时间等。最后可以通过build()方法构建一个Cache实例。

主要方法:

方法说明
newBuilder()创建一个新的 CacheBuilder 实例
from(CacheBuilderSpec spec)根据给定的规范字符串创建一个 CacheBuilder 实例
from(String spec)根据给定的规范字符串创建一个 CacheBuilder 实例
initialCapacity(int initialCapacity)设置缓存的初始容量
concurrencyLevel(int concurrencyLevel)设置并发级别,用于估计同时写入的线程数
maximumSize(long maximumSize)设置缓存的最大容量
maximumWeight(long maximumWeight)设置缓存的最大权重
weigher(Weigher<? super K1, ? super V1> weigher)设置缓存的权重计算器
weakKeys()使用弱引用存储缓存键(例如,键的引用没有被其他对象引用时,可以被垃圾回收)
weakValues()使用弱引用存储缓存值(例如,值的引用没有被其他对象引用时,可以被垃圾回收)
softValues()使用软引用存储缓存值(例如,当内存不足时,可以被垃圾回收)
expireAfterWrite(java.time.Duration duration)设置写入后过期时间
expireAfterWrite(long duration, TimeUnit unit)设置写入后过期时间
expireAfterAccess(java.time.Duration duration)设置访问后过期时间
expireAfterAccess(long duration, TimeUnit unit)设置访问后过期时间
refreshAfterWrite(java.time.Duration duration)设置写入后自动刷新时间
refreshAfterWrite(long duration, TimeUnit unit)设置写入后自动刷新时间
ticker(Ticker ticker)设置用于衡量缓存时间的时钟源
removalListener(RemovalListener<? super K1, ? super V1> listener)设置缓存条目移除监听器
recordStats()开启缓存统计信息记录
build(CacheLoader<? super K1, V1> loader)使用指定的 CacheLoader 构建缓存
build()构建缓存,如果没有指定 CacheLoader,则需要使用 get 方法手动加载缓存项

部分方法详解:

initialCapacity:设置缓存的初始容量

这个方法将通过一个整数值设置缓存的初始大小。它是一个可选的方法,如果没有指定,缓存将采用默认的初始容量。

concurrencyLevel:设置并发级别

用于估计同时写入的线程数。这个方法将通过一个整数值设置并发级别,用于内部数据结构的调整,以提高并发写入的性能。它是一个可选的方法,缺省值为 4。

maximumSize:设置缓存的最大容量

这个方法将通过一个 long 类型的值设置缓存的最大容量。当缓存的条目数达到这个容量时,会触发缓存清除策略来移除一些条目以腾出空间。它是一个可选的方法,如果没有指定最大容量,缓存将不会有大小限制。

maximumWeight:设置缓存的最大权重

这个方法将通过一个 long 类型的值设置缓存的最大权重。权重可以根据缓存条目的大小计算,通常用于缓存对象大小不同的场景。当缓存的总权重达到这个值时,会触发缓存清除策略来移除一些条目以腾出空间。它是一个可选的方法,如果没有指定最大权重,缓存将不会有权重限制。

weigher:设置缓存的权重计算器

这个方法将通过一个实现了 Weigher 接口的对象设置缓存的权重计算器。通过权重计算器,可以根据缓存条目的键和值来计算它们的权重,以便在达到最大权重时触发缓存清除策略。它是一个可选的方法,如果没有设置权重计算器,缓存将不会有权重限制。

expireAfterWrite:设置写入后过期时间

这个方法通过一个 java.time.Duration 对象设置缓存条目的写入后过期时间。过期时间从最后一次写入条目开始计算。一旦超过指定的时间,条目将被认为是过期的并被清除。这是一个可选的方法,如果没有指定过期时间,条目将不会主动过期。

expireAfterAccess:设置访问后过期时间

这个方法通过一个 java.time.Duration 对象设置缓存条目的访问后过期时间。过期时间从最后一次访问条目开始计算。一旦超过指定的时间,条目将被认为是过期的并被清除。这是一个可选的方法,如果没有指定过期时间,条目将不会主动过期。

refreshAfterWrite:设置写入后自动刷新时间

这个方法通过一个 java.time.Duration 对象设置缓存条目的自动刷新时间。自动刷新时间从最后一次写入条目开始计算。一旦超过指定的时间,当条目被访问时,缓存将自动刷新该条目,即会调用 CacheLoader 的 load 方法重新加载该条目。这是一个可选的方法,如果没有设置自动刷新时间,条目将不会自动刷新。

recordStats():开启缓存统计信息记录

这个方法用于开启缓存的统计信息记录功能。一旦开启,可以通过 Cache.stats() 方法获取缓存的统计信息,如命中率、加载次数、平均加载时间等。这是一个可选的方法,如果不开启统计信息记录,将无法获取缓存的统计信息。

注意事项:

  • maximumSize与maximumWeight不能同时设置
  • 设置maximumWeight时必须设置weigher
  • 当缓存失效后,refreshAfterWrite设置的写入后自动刷新时间不会再有用
  • 注意:expireAfterWrite、expireAfterAccess、refreshAfterWrite三个值的使用
  • 开启recordStats后,才进行统计

CacheLoader类

CacheLoader 可以被视为一种从存储系统(如磁盘、数据库或远程节点)中加载数据的方法。

CacheLoader 通常搭配refreshAfterWrite使用,在写入指定的时间周期后会调用CacheLoader 的load方法来获取并刷新为新值。

load 方法在以下情况下会被触发调用:

  • 当设置了refreshAfterWrite(写入后自动刷新时间),达到自动刷新时间时,会调用 load 方法来重新加载该键的值。
  • 调用 Cache.get(key) 方法获取缓存中指定键的值时,如果该键的值不存在,则会调用 load 方法来加载该键的值。
  • 调用 Cache.get(key, callable) 方法获取缓存中指定键的值时,如果该键的值存在,则直接返回;如果该键的值不存在,则会调用 callable 参数指定的回调函数来计算并加载该键的值。
  • 调用 Cache.getUnchecked(key) 方法获取缓存中指定键的值时,无论该键的值存在与否,都会调用 load 方法来加载该键的值。

需要注意的是,当调用 load 方法加载缓存值时,可能会发生 IO 操作或其他耗时操作,因此建议在加载操作中使用异步方式来避免阻塞主线程。另外,加载操作的实现要考虑缓存的一致性和并发性,避免多个线程同时加载同一个键的值。

CacheStats类

CacheStats 对象提供了诸如缓存命中率、加载缓存项数、缓存项回收数等统计信息的访问。

它可以通过 Cache.stats() 方法来获取,从而方便开发者监控缓存状态。

主要属性:

属性描述
hitCount缓存命中次数。表示从缓存中成功获取数据的次数
missCount缓存未命中次数。表示从缓存中未能获取到数据的次数
loadSuccessCount加载数据成功次数。表示通过 CacheLoader 成功加载数据的次数
loadExceptionCount加载数据异常次数。表示通过 CacheLoader 加载数据时发生异常的次数
totalLoadTime加载数据总耗时。表示通过 CacheLoader 加载数据的总时间
evictionCount缓存项被移除的次数,只记录因空超过设置的最大容量而进行缓存项移除的次数

RemovalListener类

RemovalListener 用于在缓存中某个值被移除时执行相应的回调操作。

可以使用 CacheBuilder.removalListener() 方法为缓存设置 RemovalListener。

RemovalListener 的使用:

  • 创建一个实现 RemovalListener 接口的类,实现 onRemoval 方法。这个方法会在缓存项被移除时被调用,接受两个参数: key 和 value。key 是被移除的缓存项的键,value 是被移除的缓存项的值。你可以根据需要在 onRemoval 方法中实现自定义的逻辑。
  • 使用 CacheBuilder 的 removalListener 方法,将创建的 RemovalListener 对象传递给它。
  • 缓存项被移除时,onRemoval 方法会自动被调用,方法会传入一个RemovalNotification 类型的参数,里面包含相应的 key 和 value等信息。你可以在这个方法中执行自定义的业务逻辑,例如日志记录、资源清理等操作。

RemovalNotification:

RemovalNotification 是 Guava 中用于表示缓存项被移除的通知的类。当在 Guava Cache 中注册了 RemovalListener 后,RemovalNotification 对象会在缓存项被移除时传递给 RemovalListener 的 onRemoval 方法。

RemovalNotification 包含了有关被移除缓存项的一些重要信息,例如键、值以及移除原因。下面是 RemovalNotification 类中常用的属性和方法:

  • getKey():获取被移除的缓存项的键。
  • getValue():获取被移除的缓存项的值。
  • getCause():获取移除原因,它是一个枚举类型RemovalCause,表示缓存项被移除的原因。

RemovalCause的可选值:

  • EXPLICIT:条目被显式删除,例如通过调用 Cache.invalidate(key) 方法。
  • REPLACED:条目被替换,例如通过调用 Cache.put(key, value) 方法重复放入相同的键。
  • EXPIRED:缓存条目由于达到了指定的过期时间而被移除。
  • SIZE:缓存条目由于超过了指定的大小限制而被移除。
  • COLLECTED:缓存条目被垃圾回收移除。这是在启用了缓存值的弱引用或软引用时发生的。

使用 RemovalNotification 可以让你在缓存项被移除时获取相关信息,并根据移除原因采取适当的处理措施。例如,你可以根据移除原因记录日志、执行清理操作、发送通知等。这样能够增强缓存的功能和可观察性。

注意事项:

  • RemovalListener 的 onRemoval 方法会在移除操作发生时同步调用,因此请确保不要在此方法中做耗时的操作,以免阻塞缓存的性能。
  • 如果在缓存移除过程中抛出任何异常,它将被捕获并记录,不会影响缓存的正常运行。
  • 需要注意的是,RemovalListener 只会在通过 Cache 的操作(如 invalidate、invalidateAll、put 进行替换)触发移除时被调用,并不会在缓存项因为过期失效而自动移除时被调用。

使用 RemovalListener 可以方便地在缓存项被移除时执行一些自定义的操作,例如清理相关资源、更新其他缓存或发送通知等。根据实际需要,合理利用 RemovalListener 可以增强缓存的功能和灵活性。

补充:

Guava自加载缓存LoadingCache使用指南实战案例

第1章:引言

大家好,我是小黑,今天我们来聊聊缓存。在Java世界里,高效的缓存机制对于提升应用性能、降低数据库负担至关重要。想象一下,如果每次数据请求都要跑到数据库里取,那服务器岂不是要累趴了?这时候,缓存就显得尤为重要了。

那么,怎么实现一个既高效又好用的缓存呢?别急,咱们今天的主角——Guava的LoadingCache就是这样一个神器。LoadingCache,顾名思义,就是能够自动加载缓存的工具。它不仅能自动载入数据,还能按需刷新,简直是懒人救星!接下来,小黑就带大家一起深入探究Guava的这个强大功能。

第2章:Guava简介

Guava是Google开源的一款Java库,提供了一堆好用的工具类,从集合操作、缓存机制到函数式编程,应有尽有。使用Guava,咱们可以写出更简洁、更高效、更优雅的Java代码。今天,小黑重点要聊的是Guava中的缓存部分。

首先,让我们来看看Guava缓存的一个基本概念:LoadingCache。LoadingCache是Guava中一个提供自动加载功能的缓存接口。它允许咱们通过一个CacheLoader来指定如何加载缓存。这就意味着,当咱们尝试从缓存中读取一个值,如果这个值不存在,LoadingCache就会自动调用预定义的加载机制去获取数据,然后将其加入到缓存中,非常智能。

来,小黑先给大家展示一个简单的LoadingCache创建示例:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
public class LoadingCacheExample {
    public static void main(String[] args) {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 最大缓存项数
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        return "Hello, " + key; // 定义缓存加载的方式
                    }
                });
        System.out.println(cache.getUnchecked("Guava")); // 输出:Hello, Guava
    }
}

在这个例子里,小黑创建了一个简单的LoadingCache实例。当咱们尝试通过getUnchecked方法获取一个缓存项时,如果这个项不存在,CacheLoader会自动加载一个新值。在这里,它就是简单地返回一个字符串。

第3章:LoadingCache基础

什么是LoadingCache呢?简单来说,它是Guava提供的一个缓存接口,能够自动加载缓存。当你尝试从缓存中读取一个值时,如果这个值不存在,LoadingCache会自动调用预定义的加载逻辑来获取这个值,然后存储到缓存中。这个过程完全自动化,省去了很多手动管理缓存的麻烦。

那么,LoadingCache的核心特性是什么呢?首先,它提供了自动的缓存加载机制,这意味着咱们不需要自己去写代码判断缓存是否存在或者过期。其次,它支持多种缓存过期策略,比如基于时间的过期、大小限制等,确保缓存的有效性。再者,LoadingCache还提供了缓存统计和监听的功能,方便咱们监控和调优缓存的使用。

来,让小黑用一个例子来展示一下LoadingCache的基本用法:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
public class LoadingCacheDemo {
    public static void main(String[] args) throws ExecutionException {
        // 创建一个CacheLoader
        CacheLoader<String, String> loader = new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                return key.toUpperCase(); // 模拟加载数据的过程
            }
        };
        // 使用CacheBuilder构建一个LoadingCache
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 设置最大缓存数为100
                .build(loader);
        // 使用缓存
        System.out.println(cache.get("hello")); // 输出: HELLO
        System.out.println(cache.get("guava")); // 输出: GUAVA
    }
}

在这个例子中,小黑创建了一个CacheLoader来定义加载数据的逻辑,这里就是简单地将字符串转换为大写。然后,使用CacheBuilder来构建一个LoadingCache实例,设置了最大缓存数为100。当调用get方法时,如果缓存中不存在对应的键值,LoadingCache会自动调用CacheLoader来加载数据,并将结果存入缓存。

第4章:创建LoadingCache

创建一个LoadingCache最关键的就是定义一个CacheLoader。这个CacheLoader指定了如何加载缓存。它就像是个工厂,当咱们请求的数据在缓存中不存在时,它就会生产出所需的数据。

那么,怎么定义这个CacheLoader呢?让小黑给你看个例子:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
public class UserCache {
    // 假设有一个用户服务,用于获取用户信息
    private static UserService userService = new UserService();
    public static void main(String[] args) throws Exception {
        // 创建CacheLoader
        CacheLoader<String, User> loader = new CacheLoader<String, User>() {
            @Override
            public User load(String userId) {
                // 从用户服务获取用户信息
                return userService.getUserById(userId);
            }
        };
        // 创建LoadingCache
        LoadingCache<String, User> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 设置最大缓存数
                .build(loader);
        // 使用缓存获取用户信息
        User user = cache.get("123"); // 如果缓存中没有,会调用load方法加载数据
        System.out.println(user);
    }
}

在这个例子中,小黑创建了一个CacheLoader来从用户服务中获取用户信息。然后,使用CacheBuilder来构建一个LoadingCache,并设置了最大缓存数量为100。当咱们通过get方法获取用户信息时,如果缓存中没有相应的数据,CacheLoader就会自动加载数据。

这个过程听起来是不是很神奇?实际上,这背后是一种非常有效的数据管理策略。通过这种方式,咱们可以减少对数据库或远程服务的直接访问,提高了应用的响应速度和效率。

第5章:LoadingCache的高级特性 自动加载和刷新机制

首先,LoadingCache的一个很棒的功能就是自动加载和刷新。这意味着当咱们请求某个键的值时,如果这个值不存在或者需要刷新,LoadingCache会自动调用CacheLoader去加载或刷新数据。

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class AutoRefreshCache {
    public static void main(String[] args) throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(1, TimeUnit.MINUTES) // 设置1分钟后刷新
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) {
                        return fetchDataFromDatabase(key); // 模拟从数据库加载数据
                    }
                });
        // 使用缓存
        System.out.println(cache.get("key1")); // 第一次加载
        // 1分钟后,尝试再次获取,将触发刷新操作
    }
    private static String fetchDataFromDatabase(String key) {
        // 模拟数据库操作
        return "Data for " + key;
    }
}

在这个例子中,咱们设置了refreshAfterWrite,这意味着每当一个键值对写入一分钟后,它就会被自动刷新。

处理异常值

有时候,加载数据可能会出现异常。LoadingCache提供了优雅的处理异常的机制。

public class ExceptionHandlingCache {
    public static void main(String[] args) throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        if ("errorKey".equals(key)) {
                            throw new Exception("Loading error");
                        }
                        return "Data for " + key;
                    }
                });
        try {
            System.out.println(cache.get("errorKey"));
        } catch (Exception e) {
            System.out.println("Error during cache load: " + e.getMessage());
        }
    }
}

这里,如果加载过程中出现异常,咱们可以捕获这个异常,并做适当的处理。

统计和监听功能

LoadingCache还提供了缓存统计和监听功能,这对于监控缓存性能和行为非常有用。

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
public class CacheMonitoring {
    public static void main(String[] args) throws Exception {
        RemovalListener<String, String> removalListener = new RemovalListener<String, String>() {
            @Override
            public void onRemoval(RemovalNotification<String, String> notification) {
                System.out.println("Removed: " + notification.getKey() + ", Cause: " + notification.getCause());
            }
        };
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)
                .removalListener(removalListener)
                .build(new CacheLoader<String, String>() {
                    // ...
                });
        // 使用缓存
        // ...
    }
}

在这个例子中,小黑设置了一个RemovalListener,用于监听缓存项的移除事件。

第6章:LoadingCache的最佳实践 配置缓存大小

合理配置缓存大小非常关键。如果缓存太小,就会频繁地加载数据,影响性能;如果太大,又可能消耗过多内存。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .maximumSize(1000) // 设置最大缓存项为1000
        .build(new CacheLoader<String, String>() {
            // ...
        });

在这个例子中,小黑设置了最大缓存项为1000。这个值需要根据实际情况和资源限制来调整。

设置合适的过期策略

LoadingCache支持基于时间的过期策略,比如访问后过期和写入后过期。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
        .expireAfterAccess(5, TimeUnit.MINUTES) // 访问后5分钟过期
        .build(new CacheLoader<String, String>() {
            // ...
        });

选择合适的过期策略可以确保缓存中的数据既不会过时,又能有效利用内存。

异常处理策略

在加载数据时可能会遇到各种异常。咱们可以设置一个合理的异常处理策略,比如记录日志、返回默认值或者重新抛出异常。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) throws Exception {
                try {
                    return fetchData(key);
                } catch (Exception e) {
                    // 处理异常
                }
            }
        });

使用软引用或弱引用

为了防止缓存占用过多内存,可以使用软引用或弱引用。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .softValues() // 使用软引用存储值
        .weakKeys() // 使用弱引用存储键
        .build(new CacheLoader<String, String>() {
            // ...
        });

使用软引用和弱引用可以帮助Java垃圾收集器在需要时回收缓存项,防止内存泄露。

监听移除通知

设置移除监听器可以帮助咱们了解缓存的行为,比如为什么某个项被移除。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .removalListener(notification -> {
            // 处理移除通知
        })
        .build(new CacheLoader<String, String>() {
            // ...
        });

通过这些最佳实践,咱们可以确保LoadingCache的高效运行,同时避免一些常见的问题。这样,咱们的Java应用就能更加稳定和高效地运行啦!

第7章:LoadingCache与Java 8的结合

好的,咱们接下来聊聊怎样把LoadingCache和Java 8的特性结合起来,用起来更顺手。

Java 8引入了很多强大的新特性,像Lambda表达式、Stream API等,这些都可以和LoadingCache搭配使用,让代码更简洁、更易读。

使用Lambda表达式简化CacheLoader

首先,咱们可以用Lambda表达式来简化CacheLoader的创建。这样代码看起来更干净,更直观。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .build(key -> fetchDataFromDatabase(key)); // 使用Lambda表达式
private static String fetchDataFromDatabase(String key) {
    // 数据库操作
    return "Data for " + key;
}

在这个例子里,小黑用Lambda表达式替代了传统的匿名内部类,使代码更加简洁。

结合Stream API处理缓存数据

接下来,咱们看看如何用Java 8的Stream API来处理LoadingCache中的数据。

LoadingCache<String, User> userCache = //... 缓存初始化
List<String> userIds = //... 用户ID列表
// 使用Stream API获取用户信息列表
List<User> users = userIds.stream()
        .map(userId -> userCache.getUnchecked(userId))
        .collect(Collectors.toList());

在这个例子中,小黑用Stream API来处理一系列用户ID,然后用map方法从缓存中获取对应的用户信息。

利用Optional处理缓存返回值

最后,Java 8引入的Optional也可以用来优雅地处理可能为空的缓存返回值。

public Optional<User> getUser(String userId) {
    try {
        return Optional.ofNullable(userCache.get(userId));
    } catch (Exception e) {
        return Optional.empty();
    }
}

在这里,小黑用Optional包装了缓存的返回值。这样一来,就能优雅地处理缓存可能返回的空值情况。

通过这些方式,结合Java 8的特性,咱们可以让LoadingCache的使用更加高效和优雅。这不仅提高了代码的可读性,还让咱们的编程体验更加流畅。

第8章:实战案例

小黑将通过一个具体的例子,展示如何在实际项目中使用LoadingCache。这个例子会模拟一个简单的场景,比如说,使用LoadingCache来缓存用户的登录次数。

假设咱们有一个应用,需要跟踪用户的登录次数。每次用户登录时,程序会增加其登录次数。为了提高性能,咱们用LoadingCache来缓存这些数据,避免每次都查询数据库。

首先,小黑定义了一个模拟的用户登录服务:

public Optional<User> getUser(String userId) {
    try {
        return Optional.ofNullable(userCache.get(userId));
    } catch (Exception e) {
        return Optional.empty();
    }
}

这个UserService类有一个addLoginCount方法,用于增加特定用户的登录次数。

接下来,小黑将展示如何使用LoadingCache来缓存登录次数:

LoadingCache<String, Integer> loginCache = CacheBuilder.newBuilder()
        .expireAfterAccess(30, TimeUnit.MINUTES) // 设置缓存30分钟后过期
        .build(new CacheLoader<String, Integer>() {
            @Override
            public Integer load(String userId) {
                return userService.addLoginCount(userId);
            }
        });
public void userLogin(String userId) {
    int count = loginCache.getUnchecked(userId);
    System.out.println("User " + userId + " login count: " + count);
}

在这个例子中,每当有用户登录,userLogin方法就会被调用。这个方法会从loginCache中获取用户的登录次数,如果缓存中没有,CacheLoader会调用UserServiceaddLoginCount来获取最新的计数,然后将其存储在缓存中。

第9章:总结

通过这些章节,咱们了解了LoadingCache的基本原理和用法,包括如何创建和配置缓存,以及如何结合Java 8的特性来优化代码。LoadingCache不仅提供了自动加载和刷新的强大功能,还有异常处理、缓存统计和监听等高级特性。

实战案例给咱们展示了LoadingCache在现实场景中的应用。不管是缓存用户信息还是统计数据,LoadingCache都能大大提高性能和用户体验。

到此这篇关于Guava自加载缓存LoadingCache使用指南的文章就介绍到这了,更多相关Guava LoadingCache缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • ByteArrayOutputStream简介和使用_动力节点Java学院整理

    ByteArrayOutputStream简介和使用_动力节点Java学院整理

    ByteArrayOutputStream 是字节数组输出流。它继承于OutputStream。这篇文章主要介绍了ByteArrayOutputStream简介和使用,需要的朋友可以参考下
    2017-05-05
  • ReentrantReadWriteLock不能锁升级的原因总结

    ReentrantReadWriteLock不能锁升级的原因总结

    今天给大家带来的是关于Java并发的相关知识,文章围绕着为什么ReentrantReadWriteLock不能锁升级展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • ArrayList和LinkedList的区别、扩容机制以及底层的实现方式

    ArrayList和LinkedList的区别、扩容机制以及底层的实现方式

    这篇文章主要介绍了ArrayList和LinkedList的区别、扩容机制以及底层的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • maven引入kabeja依赖的实现步骤

    maven引入kabeja依赖的实现步骤

    本文主要介绍了maven引入kabeja依赖的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-09-09
  • java仿枚举实例

    java仿枚举实例

    下面小编就为大家带来一篇java仿枚举实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Java实现常用加密算法——单向加密算法MD5和SHA

    Java实现常用加密算法——单向加密算法MD5和SHA

    本篇文章主要介绍了Java实现常用加密算法——单向加密算法MD5和SHA,信息加密后数据更安全,需要的朋友可以参考下。
    2016-10-10
  • mybatis初始化SqlSessionFactory失败的几个原因分析

    mybatis初始化SqlSessionFactory失败的几个原因分析

    这篇文章主要介绍了mybatis初始化SqlSessionFactory失败的几个原因分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • springboot如何连接两个数据库(多个)

    springboot如何连接两个数据库(多个)

    这篇文章主要介绍了springboot如何连接两个数据库(多个),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • SpringBoot拦截器的使用

    SpringBoot拦截器的使用

    这篇文章主要给大家分享的是SpringBoot拦截器的使用,拦截器通常通过动态代理的方式来执行。拦截器的生命周期由IoC容器管理,可以通过注入等方式来获取其他Bean的实例,使用更方便,下面文章的详细内容,需要的朋友可以参考一下
    2021-11-11
  • Spring中的@RestControllerAdvice注解使用解析

    Spring中的@RestControllerAdvice注解使用解析

    这篇文章主要介绍了Spring中的@RestControllerAdvice注解使用解析,@RestControllerAdvice 是 Spring 框架中一个用于统一处理控制器异常和返回结果的注解,它可以被用来定义全局异常处理程序和全局响应结果处理程序,需要的朋友可以参考下
    2024-01-01

最新评论