利用 Guava 进行缓存
什么是 Guava Cache
Guava 是 Google 开源的一套 Java 核心增强库,其中 Guava Cache 是一种非常优秀的本地缓存解决方案。与传统的 ConcurrentHashMap 不同,Guava Cache 提供了自动回收、过期策略、缓存统计等高级功能,非常适合在需要提升访问速度的场景中使用。
Guava Cache 适用场景
- 愿意消耗一些内存空间来提升速度
- 某些键会被查询一次以上
- 缓存中存放的数据总量不会超出内存容量
- 对数据实时性要求不高,能够容忍缓存数据短暂的不一致
基本使用
引入 Maven 依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.0.1-jre</version>
</dependency>
创建简单缓存
使用 CacheBuilder 创建一个简单的缓存实例:
Cache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(10)
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.SECONDS)
.build();
// 写入缓存
cache.put("key1", "value1");
// 读取缓存
String value = cache.getIfPresent("key1");
System.out.println(value); // 输出: value1
使用 LoadingCache(自动加载)
LoadingCache 可以在缓存未命中时自动加载数据:
LoadingCache<String, User> userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build(new CacheLoader<String, User>() {
@Override
public User load(String key) throws Exception {
return getUserFromDatabase(key);
}
});
// 第一次调用会执行 load 方法加载
User user = userCache.get("user-123");
// 第二次直接从缓存返回
User userAgain = userCache.get("user-123");
使用 Callable 模式
如果不想创建 CacheLoader,也可以使用 Callable 方式:
Cache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build();
User user = cache.get("user-456", new Callable<User>() {
@Override
public User call() throws Exception {
return getUserFromDatabase("user-456");
}
});
Java 8 Lambda 形式更简洁:
User user = cache.get("user-456", () -> getUserFromDatabase("user-456"));
缓存淘汰策略
Guava Cache 提供了多种缓存淘汰方式:
1. 基于容量淘汰
CacheBuilder.newBuilder()
.maximumSize(1000) // 最大条目数
.build();
基于权重的淘汰:
CacheBuilder.newBuilder()
.maximumWeight(50000) // 最大权重
.weigher((String key, User value) -> value.getData().length())
.build();
注意:
maximumSize和maximumWeight不能同时使用。
2. 基于时间淘汰
// 写入后指定时间过期
CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.build();
// 最后一次访问后指定时间过期
CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build();
3. 基于引用淘汰
// 使用弱引用存储键
CacheBuilder.newBuilder()
.weakKeys()
.build();
// 使用弱引用存储值
CacheBuilder.newBuilder()
.weakValues()
.build();
// 使用软引用存储值(GC在内存不足时回收)
CacheBuilder.newBuilder()
.softValues()
.build();
4. 手动清除
// 清除指定 key
cache.invalidate("key1");
// 批量清除
cache.invalidateAll(Arrays.asList("key1", "key2", "key3"));
// 清除全部
cache.invalidateAll();
高级特性
缓存刷新
refreshAfterWrite 与 expireAfterWrite 不同,它在过期后不会阻塞请求,而是异步加载新值:
LoadingCache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(new CacheLoader<String, User>() {
@Override
public User load(String key) {
return getUserFromDatabase(key);
}
@Override
public ListenableFuture<User> reload(String key, User oldValue) {
return service.submit(() -> getUserFromDatabase(key));
}
});
移除监听器
可以在缓存条目被移除时执行回调:
CacheBuilder.newBuilder()
.maximumSize(1000)
.removalListener(new RemovalListener<String, User>() {
@Override
public void onRemoval(RemovalNotification<String, User> notification) {
System.out.println(notification.getKey() + " 被移除,原因: " + notification.getCause());
}
})
.build();
缓存统计
通过 recordStats() 开启统计功能:
Cache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.recordStats()
.build();
// 获取统计信息
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate());
System.out.println("平均加载时间: " + stats.averageLoadPenalty() + " ns");
System.out.println("淘汰数量: " + stats.evictionCount());
注意事项
- Guava Cache 是本地缓存,数据存储在 JVM 堆内存中,不适合缓存大量数据
- 线程安全:Guava Cache 是线程安全的,内部使用了类似 ConcurrentHashMap 的分段锁机制
- 默认淘汰是懒淘汰:缓存的清理操作是在读写操作时触发的,可以使用
cache.cleanUp()手动触发 - Guava Cache 与 Caffeine:Spring Boot 2.0 之后推荐使用 Caffeine 替代 Guava Cache,两者 API 类似,但 Caffeine 性能更优
完整示例
import com.google.common.cache.*;
import java.util.concurrent.TimeUnit;
public class GuavaCacheExample {
public static void main(String[] args) throws Exception {
// 创建缓存
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(5) // 初始容量
.maximumSize(10) // 最大缓存数
.concurrencyLevel(4) // 并发等级
.expireAfterWrite(10, TimeUnit.SECONDS) // 写入10秒后过期
.recordStats() // 开启统计
.removalListener(notification -> {
System.out.println(notification.getKey() + " 被移除,"
+ "原因: " + notification.getCause());
})
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
System.out.println("加载数据: " + key);
return "value_for_" + key;
}
});
// 写入缓存
cache.put("key1", "value1");
// 读取缓存
System.out.println("key1 = " + cache.get("key1"));
System.out.println("key2 = " + cache.get("key2")); // 自动加载
// 获取统计
System.out.println("命中率: " + cache.stats().hitRate());
// 等待过期
Thread.sleep(11000);
System.out.println("过期后: " + cache.get("key1")); // 重新加载
}
}
总结
Guava Cache 是 Java 开发中非常实用的本地缓存工具,它提供了丰富的缓存策略和便捷的 API,适用于大多数本地缓存场景。如果项目使用的是 Spring Boot 2.0+,也可以考虑使用性能更优的 Caffeine 作为替代方案。