缓存
-
- 1、概述
- 2、核心API
-
- [2.1、CacheBuilder 配置 API](#2.1、CacheBuilder 配置 API)
- [2.2、Cache 读写 API](#2.2、Cache 读写 API)
- [2.3、LoadingCache 自动加载 API(继承自 Cache)](#2.3、LoadingCache 自动加载 API(继承自 Cache))
- [2.4、CacheStats 监控统计 API](#2.4、CacheStats 监控统计 API)
- 3、使用示例
-
- [3.1、LoadingCache 自动加载示例](#3.1、LoadingCache 自动加载示例)
- [3.2、Cache 手动加载示例](#3.2、Cache 手动加载示例)
- 4、淘汰策略
- [5、移除监听器(Removal Listener)](#5、移除监听器(Removal Listener))
- 6、手动清除缓存
- 7、缓存统计(Statistics)
1、概述
Google Guava 的 Cache 模块是 Java 中非常流行且功能强大的本地缓存实现。它不仅提供了简单的键值对存储,还支持自动加载、缓存过期、基于容量或权重的淘汰策略、软引用/弱引用以及缓存统计等高级特性。
Guava Cache 适用于以下场景:
- 愿意消耗一部分内存空间来提升访问速度。
- 预期某些键会被多次查询。
- 缓存中的数据总量不会超过可用内存。
引入依赖
xml
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version> <!-- 请检查并使用最新版本 -->
</dependency>
核心接口介绍
Guava Cache 的构建主要依靠 CacheBuilder,它采用建造者模式来配置缓存的各项参数。最终可以构建出两种类型的缓存:
- LoadingCache:在缓存未命中时,会自动调用配置好的 CacheLoader 来加载数据并放入缓存。
- Cache:需要手动插入数据,或者在获取时传入 Callable 来动态计算。
2、核心API
2.1、CacheBuilder 配置 API
| 方法名 | 说明 |
|---|---|
newBuilder() |
工厂方法,初始化 CacheBuilder 实例 |
initialCapacity(int) |
设置底层哈希表的初始容量 |
maximumSize(long) |
设置缓存最大条目数(基于 LRU 策略淘汰) |
maximumWeight(long) |
设置缓存最大总权重(需配合 weigher 使用) |
weigher(Weigher) |
自定义缓存项的权重计算规则 |
expireAfterWrite(long, TimeUnit) |
写入或更新后指定时间过期 |
expireAfterAccess(long, TimeUnit) |
读写后指定时间过期(类似于 Session 过期) |
refreshAfterWrite(long, TimeUnit) |
写入指定时间后,后续请求触发异步/同步刷出旧数据 |
weakKeys() / weakValues() |
使用弱引用存储 Key / Value |
softValues() |
使用软引用存储 Value(内存不足时垃圾回收) |
removalListener(RemovalListener) |
注册缓存移除事件的监听器 |
recordStats() |
开启指标统计(未调用则统计数据均为 0) |
build() / build(CacheLoader) |
构建 Cache 或 LoadingCache 实例 |
2.2、Cache 读写 API
| 方法名 | 说明 |
|---|---|
getIfPresent(Object key) |
获取 Key 对应的缓存;未命中返回 null |
get(K key, Callable loader) |
获取缓存;未命中时执行 loader 计算、写入缓存并返回 |
put(K key, V value) |
手动将键值对存入缓存 |
putAll(Map map) |
批量存入键值对 |
invalidate(Object key) |
手动清除指定 key 的缓存 |
invalidateAll(Iterable keys) |
批量清除指定 keys 的缓存 |
invalidateAll() |
清空缓存中的所有数据 |
size() |
返回当前缓存的大致条目数量 |
asMap() |
将 Cache 转为 ConcurrentMap 视图(操作会联动原缓存) |
stats() |
返回当前缓存的 CacheStats 统计信息 |
2.3、LoadingCache 自动加载 API(继承自 Cache)
| 方法名 | 说明 |
|---|---|
get(K key) |
获取缓存;未命中时自动调用 CacheLoader.load 加载(抛受检异常) |
getUnchecked(K key) |
与 get 功能一致,但将受检异常包装为 UncheckedExecutionException |
getAll(Iterable keys) |
批量获取;未命中时调用 CacheLoader.loadAll 进行批量加载 |
refresh(K key) |
显式触发指定 key 的数据刷新 |
2.4、CacheStats 监控统计 API
| 方法名 | 说明 |
|---|---|
hitRate() |
缓存命中率(hitCount / requestCount) |
hitCount() |
缓存命中总次数 |
missCount() |
未命中总次数 |
missRate() |
未命中率(missCount / requestCount) |
loadSuccessCount() |
成功加载新值的次数 |
loadExceptionCount() |
加载新值时抛出异常的次数 |
totalLoadTime() |
加载新值累计耗时(单位:纳秒) |
averageLoadPenalty() |
平均单次加载耗时(单位:纳秒) |
evictionCount() |
因容量限制或过期等被驱逐/淘汰的条目总数 |
3、使用示例
3.1、LoadingCache 自动加载示例
这是最常用的模式。当调用 get(key) 时,如果缓存中存在,则直接返回;如果不存在,则会自动调用 CacheLoader.load(key) 方法获取数据,存入缓存后再返回。
java
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class LoadingCacheExample {
public static void main(String[] args) throws ExecutionException {
// 创建一个 LoadingCache
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
// 设置最大并发级别
.concurrencyLevel(8)
// 设置缓存最大容量
.maximumSize(100)
// 设置写入后过期时间
.expireAfterWrite(10, TimeUnit.MINUTES)
// 构建缓存并指定 CacheLoader
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
// 这里模拟从数据库或外部接口加载数据
System.out.println("缓存未命中,正在加载数据: " + key);
return "Value_" + key;
}
});
// 第一次获取,会触发 load 方法
System.out.println(cache.get("Key1")); // 输出: 缓存未命中... -> Value_Key1
// 第二次获取,直接从缓存中取
System.out.println(cache.get("Key1")); // 输出: Value_Key1
}
}
java
缓存未命中,正在加载数据: Key1
Value_Key1
Value_Key1
3.2、Cache 手动加载示例
如果在某些场景下,你不想全局指定一种加载方式,可以使用普通的 Cache。你可以直接 put 数据,或者使用带有 Callable 的 get 方法实现"如果缓存中有则返回,没有则计算、存入缓存并返回"的逻辑。
java
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
public class CacheExample {
public static void main(String[] args) throws ExecutionException {
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.build();
// 1. 手动存入缓存
cache.put("Key1", "Value_Key1");
System.out.println(cache.getIfPresent("Key1")); // 输出: Value_Key1
// 2. 动态加载 (Get-if-Absent-Compute)
String value = cache.get("Key2", new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("缓存未命中,执行 Callable 计算数据...");
return "Value_Key2";
}
});
System.out.println(value); // 输出: Value_Key2
}
}
java
Value_Key1
缓存未命中,执行 Callable 计算数据...
Value_Key2
4、淘汰策略
4.1、基于容量淘汰
- maximumSize(long):限制缓存的最大项数。当即将达到限制时,会根据 LRU(最近最少使用)算法淘汰条目。
- maximumWeight(long):配合 Weigher 接口使用,限制缓存的总权重(例如按字节大小计算缓存容量)。
java
CacheBuilder.newBuilder()
.maximumWeight(100000)
.weigher((String key, String value) -> value.length())
.build();
4.2、基于时间淘汰
- expireAfterWrite(long, TimeUnit):缓存项在创建或最后一次更新后的指定时间后过期。
- expireAfterAccess(long, TimeUnit):缓存项在最后一次读取或写入后的指定时间后过期(常用于 Session 类似场景)。
4.3、基于引用淘汰(结合垃圾回收)
- weakKeys():使用弱引用存储键。如果没有其他强引用指向该键,该条目可能会被垃圾回收。
- weakValues():使用弱引用存储值。
- softValues():使用软引用存储值。当内存不足时,软引用指向的对象会被垃圾回收。
5、移除监听器(Removal Listener)
你可以配置一个监听器,在缓存项被移除(不论是由于过期、手动清除还是容量满了被淘汰)时执行特定操作(例如释放资源、记录日志)。
java
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
CacheBuilder.newBuilder()
.maximumSize(10)
.removalListener(new RemovalListener<String, String>() {
@Override
public void onRemoval(RemovalNotification<String, String> notification) {
System.out.println("缓存被移除 - 键: " + notification.getKey()
+ ", 值: " + notification.getValue()
+ ", 原因: " + notification.getCause());
}
})
.build();
6、手动清除缓存
你也可以随时手动干预缓存的数据:
- cache.invalidate(key):清除指定 key 的缓存。
- cache.invalidateAll(keys):批量清除指定 keys 的缓存。
- cache.invalidateAll():清空所有缓存。
7、缓存统计(Statistics)
Guava 提供了强大的统计功能,便于监控缓存的命中率和性能。在构建缓存时需要调用 recordStats()开启。
java
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheStats;
public class CacheStatsExample {
public static void main(String[] args) {
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.recordStats() // 开启统计功能
.build();
cache.put("A", "1");
cache.getIfPresent("A"); // 命中
cache.getIfPresent("B"); // 未命中
// 获取统计信息
CacheStats stats = cache.stats();
System.out.println("缓存命中率: " + stats.hitRate());
System.out.println("未命中次数: " + stats.missCount());
System.out.println("加载新值花费的平均时间: " + stats.averageLoadPenalty());
}
}
java
缓存命中率: 0.5
未命中次数: 1
加载新值花费的平均时间: 0.0