Guava Cache 本地缓存
xml com.google.guava guava 33.0.0-jre
Gradle:
```groovy
implementation 'com.google.guava:guava:33.0.0-jre'
第一个缓存示例
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100) // 最多100个条目
.build();
// 存入数据
cache.put("key1", "value1");
// 取出数据,若不存在返回null
String value = cache.getIfPresent("key1");
// 自动加载式获取(如果不存在,通过Callable加载并存入缓存)
String loaded = cache.get("key2", () -> "computedValue");
核心构建器:CacheBuilder
所有缓存实例都通过 CacheBuilder 创建。它采用链式调用的方式配置缓存行为,最终通过 build() 或 build(CacheLoader) 得到缓存对象。
CacheBuilder.newBuilder()
// 各种配置...
.build(); // 返回 Cache<K, V>
// 或
.build(loader); // 返回 LoadingCache<K, V>
LoadingCache 继承了 Cache,并额外提供了自动加载的能力,是项目中最常用的缓存形态。
缓存加载策略
按需加载:Cache.get(key, Callable)
适用于加载逻辑不固定的场景。
Cache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build();
User user = cache.get("user_123", () -> userService.getUserFromDB(123));
如果 key 已缓存则直接返回,否则执行 Callable 加载、存入缓存并返回。
自动加载:LoadingCache + CacheLoader
当所有key都遵循相同的加载逻辑时,CacheLoader 是更好的选择。
LoadingCache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(new CacheLoader<String, User>() {
@Override
public User load(String key) throws Exception {
return userService.getUserFromDB(extractId(key));
}
});
User user = cache.get("user_123");
get(key) 会检查缓存,若缺失则自动调用 load() 方法加载。该方法会缓存加载结果,并保证对同一key的并发加载只执行一次。
批量加载:loadAll
当你需要一次加载多个key时,实现 CacheLoader.loadAll() 可以大幅降低数据库/远程调用的次数。
LoadingCache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(new CacheLoader<String, User>() {
@Override
public User load(String key) { /* 单key加载 */ }
@Override
public Map<String, User> loadAll(Iterable<? extends String> keys) {
return userService.batchGetUsers(keys);
}
});
// 批量获取,优先使用loadAll
ImmutableMap<String, User> users = cache.getAll(keys);
回收(淘汰)策略
合理的回收策略能防止缓存无限增长导致内存溢出。Guava Cache 支持以下维度:
基于数量
CacheBuilder.maximumSize(10_000)
当缓存条目数接近限制时,将按照**LRU(最近最少使用)**算法淘汰旧条目。也可用 maximumWeight 结合 weigher 实现按权重回收。
基于写入时间
CacheBuilder.expireAfterWrite(10, TimeUnit.MINUTES)
条目在写入后经过一定时间自动过期。适用于“一段时间后数据必然变脏”的场景。
基于访问时间
CacheBuilder.expireAfterAccess(5, TimeUnit.MINUTES)
条目在最后一次读或写后,经过指定时间未被访问则过期。适用于维护热点数据。
基于引用
CacheBuilder.weakKeys()
.weakValues()
.softValues()
weakKeys()/weakValues():用弱引用存储键或值,当对象没有强引用时会被GC回收。softValues():用软引用存储值,仅在内存不足时被回收。
⚠️ 使用引用相关的回收时,注意 == 比较和 equals 的区别,通常需要用 weakKeys() 配合 CacheBuilder 时要格外小心键的相等性。
主动失效
除自动回收外,你还可以显式移除条目:
cache.invalidate(key)– 单个失效cache.invalidateAll(keys)– 批量失效cache.invalidateAll()– 清空整个缓存
刷新机制
与“过期后需重新加载”不同,刷新允许异步地重新加载新值,同时旧值仍然可用(直到新值加载完成)。这可以避免缓存过期瞬间的加载风暴。
LoadingCache<String, User> cache = CacheBuilder.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(loader);
// 手动触发刷新(异步)
cache.refresh(key);
refreshAfterWrite 指定写入多久后应当刷新。当某个key在过期时间后第一次被访问时,会触发异步刷新,返回旧值;刷新完成后新值替换旧值。
注意:刷新不是自动后台线程执行的,而是由访问请求触发的。若需定期刷新,可结合定时任务主动调用
cache.refresh()或使用ScheduledExecutorService。
统计信息
Guava Cache 可以记录缓存的命中率等指标,方便监控和调优:
Cache<String, User> cache = CacheBuilder.newBuilder()
.recordStats()
.maximumSize(1000)
.build();
// 使用一段时间后获取统计信息
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate());
System.out.println("平均加载时间: " + stats.averageLoadPenalty() / 1_000_000 + "ms");
主要指标包括:命中次数、未命中次数、加载成功次数、加载异常次数、加载耗时等。
监听器
通过 removalListener 可以在条目被移除时执行自定义逻辑,比如记录日志或释放资源。
Cache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.removalListener(notification -> {
System.out.println("移除原因: " + notification.getCause());
System.out.println("Key: " + notification.getKey());
System.out.println("Value: " + notification.getValue());
})
.build();