Caffeine 是一个高性能的 Java 缓存库,提供了近乎最优的命中率。Caffeine 缓存使用指南,记录几个经典场景的使用:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class BasicCacheExample {
public static void main(String[] args) {
// 构建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 设置写入10分钟后过期
.maximumSize(100) // 设置缓存的最大容量
.build();
// 存储数据
cache.put("key1", "value1");
// 检索数据
String value = cache.getIfPresent("key1");
System.out.println(value); // 输出: value1
// 另一种检索方式,如果不存在则使用提供的函数计算并存入缓存
value = cache.get("key2", k -> "value2");
System.out.println(value); // 输出: value2
}
}
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class SizeBasedCacheExample {
public static void main(String[] args) {
// 构建缓存对象,限制最大数量
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(3) // 最大3个缓存项
.build();
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
cache.put("key4", "value4"); // 加入第4个,会触发淘汰
// 由于最大数量是3,所以第一个key1可能被淘汰(取决于访问情况,这里只是示例)
System.out.println(cache.getIfPresent("key1")); // 可能为null
System.out.println(cache.getIfPresent("key4")); // 输出: value4
}
}
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class ManualLoadCacheExample {
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100)
.build();
// 手动加载:使用get方法,当缓存不存在时,通过提供的Function计算并存入缓存
String value = cache.get("key1", k -> loadFromDatabase(k));
System.out.println(value); // 输出: value1 from DB
// 模拟从数据库加载
value = cache.get("key2", k -> loadFromDatabase(k));
System.out.println(value); // 输出: key2 from DB
}
private static String loadFromDatabase(String key) {
// 模拟从数据库加载数据
return key + " from DB";
}
}
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
public class RemovalListenerExample {
public static void main(String[] args) {
// 构建缓存对象,并设置移除监听器
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(1)
.removalListener((String key, String value, RemovalCause cause) -> {
System.out.printf("Key %s was removed (%s)%n", key, cause);
})
.build();
cache.put("key1", "value1");
cache.put("key2", "value2"); // 因为最大大小为1,所以key1会被移除
// 手动移除一个键
cache.invalidate("key2");
}
}
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsyncCacheExample {
public static void main(String[] args) throws Exception {
// 构建异步加载缓存
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100)
.buildAsync(key -> loadFromDatabaseAsync(key));
// 获取值,返回的是CompletableFuture
CompletableFuture<String> future = cache.get("key1");
String value = future.get(); // 阻塞直到完成
System.out.println(value); // 输出: key1 from DB async
// 也可以使用getAll方法,同时获取多个键的值
}
private static String loadFromDatabaseAsync(String key) {
// 模拟异步加载,这里实际上还是同步的,但通常这里可能是异步操作(比如网络请求)
return key + " from DB async";
}
}
注意:
在异步加载的情况下,buildAsync方法接受一个AsyncCacheLoader,这里我们使用lambda表达式,但实际上加载操作是同步的。如果要真正的异步,可以在loadFromDatabaseAsync方法中返回一个CompletableFuture,但这里为了简单直接返回了字符串。
实际上,AsyncLoadingCache的buildAsync方法需要传入一个AsyncCacheLoader,其方法签名是K -> CompletableFuture<V>
。所以正确的异步加载示例应该是:
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsyncCacheExample {
public static void main(String[] args) throws Exception {
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100)
.buildAsync((key, executor) -> loadFromDatabaseAsync(key));
CompletableFuture<String> future = cache.get("key1");
String value = future.get();
System.out.println(value);
}
private static CompletableFuture<String> loadFromDatabaseAsync(String key) {
return CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return key + " from DB async";
});
}
}
但是注意,上面的buildAsync方法需要传入一个AsyncCacheLoader,其方法是load
,接受key和executor。我们可以使用方法引用或者lambda,但是注意参数匹配。
实际上,我们可以使用更简单的形式,如果我们的加载操作不需要Executor的话,可以使用:
.buildAsync(key -> loadFromDatabaseAsync(key));
但是loadFromDatabaseAsync必须返回CompletableFuture。所以修正后的异步加载示例:
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsyncCacheExample {
public static void main(String[] args) throws Exception {
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(100)
.buildAsync(AsyncCacheExample::loadFromDatabaseAsync);
CompletableFuture<String> future = cache.get("key1");
String value = future.get();
System.out.println(value); // 输出: key1 from DB async
}
private static CompletableFuture<String> loadFromDatabaseAsync(String key) {
return CompletableFuture.supplyAsync(() -> {
// 模拟异步加载数据
return key + " from DB async";
});
}
}
这样,加载操作就会在CompletableFuture的默认线程池(ForkJoinPool.commonPool())中异步执行。
根据实际需求,可以组合不同的策略(如过期、大小限制、刷新等)来构建缓存。
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version> <!-- 请检查最新版本 -->
</dependency>
场景1:简单缓存(设置过期时间)
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class SimpleCacheExample {
// 创建缓存实例
private static final Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入10分钟后过期
.maximumSize(100) // 最多缓存100个元素
.build();
public static void main(String[] args) {
// 存储数据
cache.put("user:123", "John Doe");
// 获取数据
String user = cache.getIfPresent("user:123");
System.out.println("User: " + user); // 输出: User: John Doe
// 获取数据,如果不存在则通过函数计算
String userData = cache.get("user:456", key -> fetchUserFromDatabase(key));
System.out.println("User 456: " + userData);
}
private static String fetchUserFromDatabase(String key) {
// 模拟数据库查询
return "User from DB: " + key.split(":")[1];
}
}
场景2:自动刷新缓存
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class RefreshCacheExample {
// 创建自动刷新缓存
private static final LoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.refreshAfterWrite(1, TimeUnit.MINUTES) // 写入1分钟后自动刷新
.maximumSize(100)
.build(key -> fetchDataFromSource(key));
public static String getData(String key) {
return cache.get(key);
}
private static String fetchDataFromSource(String key) {
// 模拟从数据源获取数据
System.out.println("Fetching data for: " + key);
return "Data for " + key + " at " + System.currentTimeMillis();
}
public static void main(String[] args) throws InterruptedException {
System.out.println(getData("key1"));
Thread.sleep(30000); // 等待30秒
System.out.println(getData("key1")); // 可能会触发刷新
}
}
场景3:缓存事件监听
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import java.util.concurrent.TimeUnit;
public class EventListenerCacheExample {
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.SECONDS)
.maximumSize(3)
.removalListener((String key, String value, RemovalCause cause) -> {
System.out.printf("Key %s was removed (%s)%n", key, cause);
})
.build();
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
cache.put("key4", "value4"); // 这将触发key1的移除
cache.invalidate("key2"); // 手动移除key2
try {
Thread.sleep(6000); // 等待6秒让缓存过期
cache.cleanUp(); // 触发过期清理
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
场景4:统计缓存命中率
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
public class StatsCacheExample {
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.recordStats() // 开启统计功能
.build();
cache.put("key1", "value1");
for (int i = 0; i < 10; i++) {
cache.getIfPresent("key1"); // 命中
cache.getIfPresent("nonexistent"); // 未命中
}
CacheStats stats = cache.stats();
System.out.println("命中次数: " + stats.hitCount());
System.out.println("命中率: " + stats.hitRate());
System.out.println("未命中次数: " + stats.missCount());
System.out.println("加载成功次数: " + stats.loadSuccessCount());
}
}
场景5:基于权重的缓存
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class WeightedCacheExample {
static class HeavyObject {
private final byte[] data = new byte[1024 * 1024]; // 1MB
private final String name;
public HeavyObject(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
Cache<String, HeavyObject> cache = Caffeine.newBuilder()
.maximumWeight(10_000_000) // 最大权重10MB
.weigher((String key, HeavyObject value) -> value.data.length)
.build();
for (int i = 0; i < 15; i++) {
cache.put("obj" + i, new HeavyObject("Object " + i));
}
System.out.println("缓存大小: " + cache.estimatedSize());
System.out.println("统计信息: " + cache.stats());
}
}
场景6:API响应缓存
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class ApiResponseCache {
private final AsyncLoadingCache<String, String> apiCache;
public ApiResponseCache() {
this.apiCache = Caffeine.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES)
.maximumSize(1000)
.buildAsync((key, executor) -> fetchApiResponseAsync(key));
}
public CompletableFuture<String> getApiResponse(String endpoint) {
return apiCache.get(endpoint);
}
private CompletableFuture<String> fetchApiResponseAsync(String endpoint) {
return CompletableFuture.supplyAsync(() -> {
// 模拟API调用
try {
Thread.sleep(200); // 模拟网络延迟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Response from " + endpoint + " at " + System.currentTimeMillis();
});
}
public static void main(String[] args) throws Exception {
ApiResponseCache cache = new ApiResponseCache();
// 第一次调用,会实际请求API
cache.getApiResponse("/users/123").thenAccept(response -> {
System.out.println("Response: " + response);
});
// 短时间内再次调用,会从缓存获取
Thread.sleep(100);
cache.getApiResponse("/users/123").thenAccept(response -> {
System.out.println("Cached response: " + response);
});
Thread.sleep(1000); // 等待异步操作完成
}
}
场景7:数据库查询缓存
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class DatabaseCache {
private final LoadingCache<Long, User> userCache;
public DatabaseCache() {
this.userCache = Caffeine.newBuilder()
.expireAfterAccess(30, TimeUnit.MINUTES)
.maximumSize(10000)
.build(this::loadUserFromDatabase);
}
public User getUserById(long userId) {
return userCache.get(userId);
}
public void updateUser(User user) {
// 更新数据库
updateUserInDatabase(user);
// 使缓存失效
userCache.invalidate(user.getId());
}
private User loadUserFromDatabase(long userId) {
// 模拟数据库查询
System.out.println("Querying database for user: " + userId);
return new User(userId, "User_" + userId, "user" + userId + "@example.com");
}
private void updateUserInDatabase(User user) {
// 模拟数据库更新
System.out.println("Updating user in database: " + user.getId());
}
static class User {
private final long id;
private final String name;
private final String email;
public User(long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public long getId() {
return id;
}
// 其他getter方法...
}
public static void main(String[] args) {
DatabaseCache cache = new DatabaseCache();
// 第一次查询,会访问数据库
User user1 = cache.getUserById(123);
System.out.println("User: " + user1.getName());
// 第二次查询相同ID,会从缓存获取
User user2 = cache.getUserById(123);
System.out.println("User from cache: " + user2.getName());
// 更新用户
User updatedUser = new User(123, "Updated Name", "updated@example.com");
cache.updateUser(updatedUser);
// 再次查询,会重新加载数据
User user3 = cache.getUserById(123);
System.out.println("Updated user: " + user3.getName());
}
}
Caffeine 提供了丰富的缓存策略和配置选项,适用于各种场景: