菜单

Administrator
发布于 2026-05-18 / 1 阅读
0
0

利用 Guava 进行缓存

利用 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();

注意:maximumSizemaximumWeight 不能同时使用。

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();

高级特性

缓存刷新

refreshAfterWriteexpireAfterWrite 不同,它在过期后不会阻塞请求,而是异步加载新值:

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());

注意事项

  1. Guava Cache 是本地缓存,数据存储在 JVM 堆内存中,不适合缓存大量数据
  2. 线程安全:Guava Cache 是线程安全的,内部使用了类似 ConcurrentHashMap 的分段锁机制
  3. 默认淘汰是懒淘汰:缓存的清理操作是在读写操作时触发的,可以使用 cache.cleanUp() 手动触发
  4. 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 作为替代方案。


评论