Guava中的缓存(Cache Builder)实现

Guava中的缓存(CacheBuilder)提供了强大的本地缓存能力,包括线程安全、多种缓存回收策略、缓存命中率统计等功能。适用于以空间换时间的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Guava中的缓存(Cache Builder)实现

概念

缓存在很多场景中是必不可少的,例如,计算或检索一个只的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。

Guava中的缓存是本地缓存的实现,与ConcurrentMap相似,但不完全一样。最基本的区别就是,ConcurrentMap会一直保存添加进去的元素,除非你主动remove掉。而Guava Cache为了限制内存的使用,通常都会设置自动回收。在某些场景下,尽管LoadingCache不回收元素,但它还是很有用的,因为它会自动加载缓存。

Guava Cache的使用场景:

  • 以空间换取时间,就是你愿意用内存的消耗来换取读取性能的提升
  • 你已经预测到某些数据会被频繁的查询
  • 缓存中存放的数据不会超过内存空间

Guava Cache是单个应用运行时的本地缓存,单机版的缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached或Redis。

Guava Cache

Guava Cache是一个本地缓存。

优点

  • 线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素。
  • 提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。
  • 监控缓存加载/命中情况。
  • 集成了多部操作,调用get方式,可以在未命中缓存的时候,从其他地方获取数据源(DB,redis),并加载到缓存中。

缺点

  • Cuava Cache的超时机制不是精确的

示例代码:

public static void main(String[] args) throws ExecutionException, InterruptedException{
        //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
        LoadingCache<Integer,Student> studentCache
                //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
                = CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置写缓存后8秒钟过期
                .expireAfterWrite(8, TimeUnit.SECONDS)
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(100)
                //设置要统计缓存的命中率
                .recordStats()
                //设置缓存的移除通知
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                    }
                })
                //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                .build(
                        new CacheLoader<Integer, Student>() {
                            @Override
                            public Student load(Integer key) throws Exception {
                                System.out.println("load student " + key);
                                Student student = new Student();
                                student.setId(key);
                                student.setName("name " + key);
                                return student;
                            }
                        }
                );
 
        for (int i=0;i<20;i++) {
            //从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
            Student student = studentCache.get(1);
            System.out.println(student);
            //休眠1秒
            TimeUnit.SECONDS.sleep(1);
        }
 
        System.out.println("cache stats:");
        //最后打印缓存的命中率等 情况
        System.out.println(studentCache.stats().toString());
    }

Cache

Cache是Guava提供的最基本缓存接口,创建一个Cache很简单

@Test
    public void cacheCreateTest(){
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) //设置缓存最大容量
                .expireAfterWrite(1,TimeUnit.MINUTES) //过期策略,写入一分钟后过期
                .build();
        cache.put("a","a1");
        String value = cache.getIfPresent("a");
    }

Cache是通过CacheBuilder对象来build出来的,build之前可以设置一系列的参数

LoadingCache

LoadingCache继承自Cache,当从缓存中读取某个key时,假如没有读取到值,LoadingCache会自动进行加载数据到缓存

public void loadingCacheTest() throws ExecutionException {
        LoadingCache<String,String> loadingCache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .refreshAfterWrite(Duration.ofMillis(10))//10分钟后刷新缓存的数据
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        Thread.sleep(1000);
                        System.out.println(key + " load data");
                        return key + " add value";
                    }
                });
        System.out.println(loadingCache.get("a"));
        System.out.println(loadingCache.get("b"));
    }

运行结果:

a load data
a add value
b load data
b add value

LoadingCache也是通过CacheBuilder创建出来的,只不过创建的时候,需要在build方法里面传入CacheLoader

CacheLoader类的load方法就是在key找不到的情况下,进行数据自动加载的

Cache常用参数

下面我们看一下Guava Cache在使用时常用的属性,下面的属性对Cache和LoadingCache都适用

1、容量初始化

public void initialCapacityTest(){
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .initialCapacity(1024) //初始容量
                .build();
    }

2、最大容量

最大容量可以通过两种维度来设置

  • maximumSize 最大记录数,存储数据的个数
  • maximumWeight 最大容量,存储数据的大小
  @Test
    public void maxSizeTest(){
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(2)//缓存最大个数
                .build();
        cache.put("a","1");
        cache.put("b","2");
        cache.put("c","3");

        System.out.println(cache.getIfPresent("a"));
        System.out.println(cache.getIfPresent("b"));
        System.out.println(cache.getIfPresent("c"));

        Cache<String,String> cache1 = CacheBuilder.newBuilder()
                .maximumWeight(1024 * 1024 * 1024)//最大容量为1M
                //用来计算容量的Weigher
                .weigher(new Weigher<String, String>() {
                    @Override
                    public int weigh(String key, String value) {
                        return key.getBytes().length + value.getBytes().length;
                    }
                })
                .build();
        cache1.put("x","1");
        cache1.put("y","2");
        cache1.put("z","3");

        System.out.println(cache1.getIfPresent("x"));
        System.out.println(cache1.getIfPresent("y"));
        System.out.println(cache1.getIfPresent("z"));

    }

运行结果:

null
2
3
1
2
3

我们设置缓存的最大记录为2,当我们添加三个元素进去后,会把前面添加的元素覆盖

3、过期时间

  • expireAfterWrite 写入后多长时间,数据就过期了
  • expireAfterAccess 数据多长时间没有被访问,就过期
@Test
    public void expireTest() throws InterruptedException {
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)//缓存最大个数
                .expireAfterWrite(5,TimeUnit.SECONDS)//写入后5分钟过期
                .build();
        cache.put("a","1");
        int i = 1;
        while(true){
            System.out.println("第" + i + "秒获取到的数据为:" + cache.getIfPresent("a"));
            i++;
            Thread.sleep(1000);
        }
    }

运行结果

第1秒获取到的数据为:1
第2秒获取到的数据为:1
第3秒获取到的数据为:1
第4秒获取到的数据为:1
第5秒获取到的数据为:1
第6秒获取到的数据为:null
第7秒获取到的数据为:null
第8秒获取到的数据为:null
第9秒获取到的数据为:null

从运行结果可以看出来,写入数据后的第6秒就开始获取不到数据了

   @Test
    public void expireAfterAccessTest() throws InterruptedException {
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)//缓存最大个数
                .expireAfterAccess(5,TimeUnit.SECONDS)//5秒没有被访问,就过期
                .build();
        cache.put("a","1");
        Thread.sleep(3000);
        System.out.println("休眠3秒后访问:" + cache.getIfPresent("a"));
        Thread.sleep(4000);
        System.out.println("休眠4秒后访问:" + cache.getIfPresent("a"));
        Thread.sleep(5000);
        System.out.println("休眠5秒后访问:" + cache.getIfPresent("a"));

    }

运行结果:

休眠3秒后访问:1
休眠4秒后访问:1
休眠5秒后访问:null

从运行结果可以看出,只要超过了设定的时间没有人访问,缓存的数据就会过期

缓存回收

一个残酷的现实是,我们几乎一定没有足够的内存缓存所有数据。你你必须决定:什么时候某个缓存项就不值得保留了?Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。

1.基于容量的回收(size-based eviction)

如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。

另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。

2.定时回收(Timed Eviction)

CacheBuilder提供两种定时回收的方法:

expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。

expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

3.基于引用的回收(Reference-based Eviction)

通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用键的缓存用而不是equals比较键。

CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用值的缓存用而不是equals比较值。

CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。

显式清除

任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

个别清除:Cache.invalidate(key)
批量清除:Cache.invalidateAll(keys)
清除所有缓存项:Cache.invalidateAll()

删除监听器

通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知RemovalNotification,其中包含移除原因RemovalCause、键和值。

常用方法

方法作用
V getIfPresent(Object key)获取缓存中key对应的value,如果缓存没命中,返回null。
V get(K key) throws ExecutionException获取key对应的value,若缓存中没有,则调用LocalCache的load方法,从数据源中加载,并缓存。
void put(K key, V value)如果缓存有值,覆盖,否则,新增
void putAll(Map m)循环调用单个的方法
void invalidate(Object key)删除缓存
void invalidateAll()清楚所有的缓存,相当远map的clear操作
long size()获取缓存中元素的大概个数。为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素
CacheStats stats()缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等
asMap()获得缓存数据的ConcurrentMap快照
cleanUp()清空缓存
refresh(Key)刷新缓存,即重新取缓存数据,更新缓存
ImmutableMap getAllPresent(Iterable keys)一次获得多个键的缓存值
CacheBuilder:类缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。CacheBuilder在build方法中,会把前面设置的参数,全部传递给LocalCache,它自己实际不参与任何计算。这种初始化参数的方法值得借鉴,代码简洁易读。
CacheLoader:抽象类用于从数据源加载数据,定义load、reload、loadAll等操作
Cache:接口定义get、put、invalidate等操作,这里只有缓存增删改的操作,没有数据加载的操作
AbstractCache:抽象类实现Cache接口。其中批量操作都是循环执行单次行为,而单次行为都没有具体定义
LoadingCache:接口继承自Cache。定义get、getUnchecked、getAll等操作,这些操作都会从数据源load数据
AbstractLoadingCache:抽象类继承自AbstractCache,实现LoadingCache接口
LocalCache:类整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操作方法
LocalManualCacheLocalCache内部静态类,实现Cache接口。其内部的增删改缓存操作全部调用成员变量 localCache(LocalCache类型)的相应方法。
LocalLoadingCacheLocalCache内部静态类,继承自LocalManualCache类,实现LoadingCache接口。
其所有操作也是调用成员变量localCache(LocalCache类型)的相应方法。
CacheStats缓存加载/命中统计信息

引用:

https://blog.csdn.net/z5234032/article/details/52584277

https://blog.csdn.net/liuxiao723846/article/details/108392072

https://www.zhihu.com/search?type=content&q=Guava%E4%B8%ADCacheBuilder

<think>好的,我现在需要帮助用户了解如何在Spring Boot中使用Guava实现@Cacheable注解的功能。用户提到了参考内容中的Couchbase缓存配置示例,可能希望类似地配置Guava。首先,我应该回忆一下Spring Boot的缓存抽象机制,以及如何集成不同的缓存实现。 首先,Spring Boot通过@EnableCaching开启缓存支持,并通过配置CacheManager来使用特定的缓存库。用户提到的Couchbase例子中,自动配置了CouchbaseCacheManager,而用户现在需要的是Guava实现Guava属于内存缓存,通常使用CacheBuilder来配置缓存参数。 接下来,我需要确定步骤。用户需要一个示例教程,所以步骤应该包括添加依赖、配置CacheManager、使用@Cacheable注解等。需要确保代码示例正确,比如GuavaCacheBuilder设置过期时间、最大大小等,并且通过Spring的配置类来创建GuavaCacheManager。 然后,检查是否需要处理依赖冲突。Guava的依赖是com.google.guava:guava,而Spring Boot可能有自己的缓存starter,所以需要添加spring-boot-starter-cache。同时,确保在配置类中使用@EnableCaching。 可能还需要考虑不同的缓存配置,比如用户提到的Couchbase例子中有多个缓存名称和不同的Bucket,但Guava的情况下可能需要在同一个CacheManager中配置不同的缓存参数。不过,Guava的每个缓存可能需要单独配置,或者使用统一的配置。这可能需要使用Spring的CacheSpec来支持不同的配置,但默认情况下可能每个缓存使用相同的设置。如果用户需要不同的缓存有不同的配置,可能需要自定义多个CacheManager,但这样比较复杂。不过用户的问题可能只需要基础示例,所以先提供统一配置,再提示高级用法。 然后,用户的问题还提到参考内容中的application.properties配置,比如spring.cache.cache-names。所以在Guava中,可能也需要在配置文件中指定缓存名称,同时在配置类中设置各个缓存的参数。例如,通过CacheBuilder构建缓存,然后通过GuavaCacheManager来管理。 最后,需要验证代码示例是否正确。比如,GuavaCacheManager的创建,是否在配置类中正确使用Bean注解,以及是否在Service类中正确使用@Cacheable注解。同时,确保application.properties中启用了缓存名称,并可能配置其他Guava参数。 可能遇到的问题包括依赖版本不兼容,或者缓存配置未生效。所以需要提醒用户检查依赖版本,并确保配置类被正确扫描。此外,如果用户需要不同的缓存有不同的过期时间,可能需要更复杂的配置,比如使用CompositeCacheManager或者自定义CacheManager,但基础示例可能不需要这些,可以放在相关问题中。</think>在Spring Boot中使用Guava实现`@Cacheable`注解的功能,可以通过以下步骤实现: --- ### **1. 添加依赖** 在`pom.xml`中引入Guava和Spring Cache的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> <!-- 使用最新版本 --> </dependency> ``` --- ### **2. 启用缓存支持** 在启动类或配置类添加`@EnableCaching`注解: ```java @SpringBootApplication @EnableCaching public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` --- ### **3. 配置Guava缓存管理器** 创建自定义的`CacheManager` Bean以使用Guava: ```java @Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { GuavaCacheManager cacheManager = new GuavaCacheManager(); // 默认缓存配置:最大100条目,30秒后过期 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterWrite(30, TimeUnit.SECONDS); cacheManager.setCacheBuilder(builder); return cacheManager; } } ``` --- ### **4. 在Service中使用@Cacheable** ```java @Service public class UserService { @Cacheable(value = "users", key = "#id") public User getUserById(String id) { // 模拟耗时操作 return new User(id, "User " + id); } } ``` --- ### **5. 配置文件(可选)** 在`application.properties`中指定缓存名称: ```properties spring.cache.cache-names=users,products ``` --- ### **高级配置:不同缓存不同参数** 若需为不同缓存设置不同规则(如参考示例中的`cache3`[^1]),可自定义多个`CacheBuilder`: ```java @Bean public CacheManager cacheManager() { GuavaCacheManager cacheManager = new GuavaCacheManager(); cacheManager.setCacheSpecification("users: maximumSize=100, expireAfterAccess=30s"); cacheManager.setCacheSpecification("products: maximumSize=500, expireAfterWrite=1h"); return cacheManager; } ``` --- ### **验证缓存生效** 调用`getUserById`方法时,首次会执行方法体,后续相同参数直接从Guava缓存中返回结果。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值