好用的Java内存缓存库,Guava Cache

一、引言

内存缓存是一种在内存中临时存储数据的机制,它能加速数据访问,提升应用程序的响应性能。

通过将经常访问的数据保存在快速访问的内存中,可以减少昂贵的磁盘或网络访问。内存缓存还能减轻数据源负载,提高系统稳定性和可扩展性。

然而,使用内存缓存需要仔细考虑缓存策略、过期时间、一致性等问题,以确保缓存的有效性。

合理的缓存设计和配置能充分发挥内存缓存的优势,提供更好的应用体验。

作为一个开发,多多少少都对内存缓存有一些了解。可能更多的人在工作中接触的更多的是memcacheredis这些内存缓存系统。这些系统都是需要额外部署的,需要使用客户端链接,查询和存储的时候也会增加我们的网络开销。

今天我们来认识一个基于JVM的内存缓存库:Guava Cache

二、Guava Cache是什么

Guava Cache是Google Guava库中提供的一个内存缓存库。旨在提供简单而高效的内存缓存解决方案。为Java开发人员提供了一种方便的方式来实现内存缓存,以改善应用程序的性能和响应性。

Guava Cache提供了一种可配置的缓存机制,可以将经常访问的数据存储在内存中,以提高数据访问的速度和性能。它支持设置缓存的最大大小、过期策略、自动加载数据等功能。缓存可以自动管理数据的过期和淘汰策略,以确保缓存中的数据是最新和有效的。

使用Guava Cache,开发人员可以轻松地在应用程序中集成内存缓存功能,以加速数据访问和提升应用程序的性能。它适用于各种本地缓存需求,并提供了简单的API和配置选项来满足不同的使用场景。通过减少对耗时的数据源访问,Guava Cache可以显著提升应用程序的响应性能,并减轻对后端资源的负载。

2.1 Guava Cache 的主要概念和特点

graph LR A(Guava Cache 的主要概念和特点) B(Google Guava 库中的一个功能模块) C(简单而高效的内存缓存解决方案) D(提高访问速度和性能) E(配置选项) EA(最大大小) EB(过期策略) EC(数据加载方式) F(自动管理) FA(过期和淘汰策略) FB(数据有效性和一致性) G(优势) GA(API简单) GB(减少耗时的数据源访问) GC(提升应用程序的响应性能) H(强大,性能改善) A ---> B A ---> C A ---> D A ---> E E ---> EA E ---> EB E ---> EC A ---> F F ---> FA F ---> FB A ---> G G ---> GA G ---> GB G ---> GC A ---> H style GB fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style GC fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style GA fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style FB fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style FA fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style EB fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style EC fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style EA fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px style G fill:#00FFFF,stroke:#00FFFF,stroke-width:2px style H fill:#E6E6FA,stroke:#E6E6FA,stroke-width:2px
  • Guava Cache 是 Google Guava 库中的一个功能模块。

  • Guava Cache 提供了简单而高效的内存缓存解决方案。

  • Guava Cache 允许将经常访问的数据存储在内存中,以提高访问速度和性能。

  • 可以配置 Guava Cache 的最大大小、过期策略和数据加载方式。

  • 缓存中的数据可以自动管理过期和淘汰策略,确保数据的有效性和一致性。

  • Guava Cache 适用于各种本地缓存需求,提供简单的 API 和配置选项。

  • 使用 Guava Cache 可以减少对耗时的数据源访问,提升应用程序的响应性能。

  • Guava Cache 是一个简单而强大的内存缓存库,改善应用程序的性能和响应性。

三、怎么使用Guava Cache

在项目中使用Guava Cache的时候,实际是有一些固定的操作姿势,下面我们来认识一下一般情况下我们都是怎么操作的。

3.1 使用流程图

sequenceDiagram participant A1 as Java项目 participant A as 任务线程 participant B as Guava Cache participant C as 监听器线程 A1 ->> A1: 引入依赖 A1 ->> A: 启动任务 A ->> B: 创建Cache实例 B ->> B: 数据加载 A ->> B: 存储数据 A ->> B: 获取数据 A ->> B: 移除数据 A ->> B: 清空Cache C ->> B: 添加监听器 C ->> B: 数据过期或者被移除逻辑

3.2 使用的详细步骤分解

  1. 添加Guava库依赖:在你的项目中添加Guava库的依赖,以便能够使用Guava Cache。你可以从Maven中央仓库或Gradle依赖中心下载Guava相关的库文件。
java 复制代码
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.2-jre</version>
</dependency>
  1. 创建Cache实例:使用Guava Cache提供的CacheBuilder类创建一个Cache实例。你可以通过链式调用方法来配置Cache的属性,例如最大大小、过期策略等。
java 复制代码
Cache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(100)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();

上面代码代码创建了一个具有最大大小限制和过期时间的缓存对象,我们来详细分析一下:

  • 首选 Cache<Key, Value> cache = CacheBuilder.newBuilder():这行代码创建了一个新的 CacheBuilder 对象,并将其分配给变量 cache。CacheBuilder 是 Guava 提供的用于构建缓存对象的构建器类。

  • 然后 .maximumSize(100):这行代码设置了缓存的最大大小为 100。这意味着当缓存中的元素数量达到最大值时,新的元素将会替换掉最旧的元素。

  • 其次 .expireAfterWrite(10, TimeUnit.MINUTES):这行代码设置了缓存项在写入后的存活时间为 10 分钟。换句话说,如果某个缓存项在 10 分钟内没有被读或写操作访问,那么该缓存项将会被自动移除。

  • 最后 .build():这行代码完成了缓存对象的构建,并返回最终的 Cache<Key, Value> 对象。

  1. 存储数据:使用put(key, value)方法将数据存储到Cache中。你可以使用自定义的键(Key)和值(Value)类型。
java 复制代码
cache.put(key, value);
  1. 获取数据:使用getIfPresent(key)方法从Cache中获取数据。如果数据存在于Cache中,则返回对应的值;否则返回null。
java 复制代码
Value value = cache.getIfPresent(key);
  1. 数据加载:如果需要自动加载数据到Cache中,你可以使用get(key, Callable)方法。当缓存中不存在对应的值时,Guava Cache会自动调用提供的Callable对象来加载数据。
java 复制代码
Value value = cache.get(key, new Callable<Value>() {
    public Value call() throws Exception {
        // 数据加载逻辑
        return loadDataByDb(key);
    }
});
  1. 移除数据:你可以使用invalidate(key)方法从Cache中移除指定的键值对。
java 复制代码
cache.invalidate(key);
  1. 清空Cache:如果需要清空整个Cache,你可以使用invalidateAll()方法。
java 复制代码
cache.invalidateAll();
  1. 监听器:Guava Cache还支持添加监听器,以便在数据被移除或过期时执行自定义逻辑。你可以实现RemovalListener接口并注册监听器。
java 复制代码
RemovalListener<Key, Value> listener = new RemovalListener<Key, Value>() {
    public void onRemoval(RemovalNotification<Key, Value> notification) {
        // 执行自定义逻辑
    }
};
cacheBuilder.removalListener(listener);

四、实际操作中,Google Guava的几种过期策略如何实现

Google Guava 提供了多种过期策略来管理缓存项的过期和失效。下面来介绍一下 Guava 中几种常用的过期策略及其实现方式。

老规矩,先来张图看看都有那些过期策略:

graph LR A(Google Guava过期策略) B(基于时间) C(写入时间) D(访问时间) E(基于大小) F(基于引用) G(基于提醒) A ---> B B ---> C B ---> D A ---> E A ---> F A ---> G style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px style G fill:#00FFFF,stroke:#00FFFF,stroke-width:2px

4.1 基于时间的过期策略

可以通过 expireAfterWrite() 和 expireAfterAccess() 方法来设置缓存项的写入后过期时间和最后访问后过期时间。

  • expireAfterWrite(duration, unit):设置缓存项在写入后的过期时间。过期时间从写入操作发生后开始计算。
java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES) // 缓存项在写入后10分钟过期
        .build();

cache.put("key", "value");

// 在10分钟内访问缓存项,它仍然有效
String value = cache.getIfPresent("key");

// 10分钟后,缓存项过期,再次访问将返回null
value = cache.getIfPresent("key");
  • expireAfterAccess(duration, unit):设置缓存项在最后访问后的过期时间。过期时间从最后一次访问操作发生后开始计算。
java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
        .expireAfterAccess(5, TimeUnit.MINUTES) // 缓存项在最后一次访问后5分钟过期
        .build();

cache.put("key", "value");

// 在5分钟内多次访问缓存项,它仍然有效
String value = cache.getIfPresent("key");
value = cache.getIfPresent("key");
value = cache.getIfPresent("key");

try {
    // 休眠5分钟
    Thread.sleep(5 * 60 * 1000);
} catch (InterruptedException e) {
    // 处理中断异常
    e.printStackTrace();
}

// 5分钟后,没有再次访问缓存项,它过期并被自动移除
// 再次尝试获取缓存项将返回null
value = cache.getIfPresent("key"); // 返回null

注意:这两种策略可以根据指定的时间单位(如分钟、小时、天等)来设置过期时间。

4.2 基于大小的过期策略

可以通过 maximumSize() 方法设置缓存的最大大小限制。当缓存项数量达到最大值时,新的元素将会替换掉最旧的元素。

java 复制代码
Cache<String, String> cache = CacheBuilder.newBuilder()
        .maximumSize(100) // 缓存最大容量为100
        .build();

for (int i = 0; i < 150; i++) {
    cache.put("key" + i, "value" + i);
}

// 在缓存容量达到100后,新的缓存项将替换最旧的缓存项
String value = cache.getIfPresent("key0"); // 返回null,因为"key0"已被替换
value = cache.getIfPresent("key100"); // 返回"value100",因为它仍然在缓存中

4.3 基于引用的过期策略

Guava 提供了 CacheBuilder 类中的 weakKeys()weakValues() 方法,允许使用弱引用来存储缓存的键和值。当键或值不再被其他强引用引用时,缓存项将会被自动移除。

  • weakKeys():使用弱引用存储缓存的键。

  • weakValues():使用弱引用存储缓存的值。

这些弱引用策略可用于在内存受限的情况下,允许缓存项根据垃圾回收的需要进行回收。

java 复制代码
Cache<String, Object> cache = CacheBuilder.newBuilder()
        .weakKeys() // 使用弱引用存储缓存的键
        .weakValues() // 使用弱引用存储缓存的值
        .build();

String key = new String("key");
Object value = new Object();

cache.put(key, value);

// 当key不再有其他强引用时,缓存项将被自动移除
value = cache.getIfPresent(key); // 返回null,因为key已没有强引用

4.4 基于提醒的过期策略

通过 CacheBuilder 类的 expireAfterWrite() 方法结合 CacheBuilderSpec 类的 refreshAfterWrite() 方法,可以实现基于提醒的过期策略。该策略允许在缓存项过期时异步地加载新的值,并返回旧值。这样可以确保在缓存项过期时仍然能够返回旧值,同时异步加载新值,提高了缓存的命中率和性能。

java 复制代码
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES) // 缓存项在写入后10分钟过期
        .refreshAfterWrite(1, TimeUnit.MINUTES) // 缓存项在写入后1分钟后异步刷新
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                return "value"; // 在缓存项过期时异步加载新的值
            }
        });

String value = cache.get("key");

// 10分钟内,获取缓存项将返回旧值,并异步加载新的值
value = cache.get("key");

// 10分钟后,缓存项过期,获取缓存项将返回新的值
value = cache.get("key");

五、总结

Google Guava是一个功能强大的Java工具库,提供了丰富的工具和集合类、函数式编程支持、缓存和并发处理等功能,帮助开发人员编写高效、可靠和易维护的Java代码;而且一直在维护迭代升级,行业良心。

更多操作请查看官方文档

最后:如果你从本篇文章中学到一点点东西,麻烦发财的小手点一下赞,如有疑问或者错误,欢迎提出和指正。

相关推荐
chuanauc18 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴34 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao41 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78744 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野7 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法
小阳拱白菜8 小时前
java异常学习
java