Guava系列之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

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

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

LoadingCache

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

    @Test
    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找不到的情况下,进行数据自动加载的

下面我们看一下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

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

回收策略

在上面我们讲了两种回收策略

  • expireAfterWrite 写入多长时间后就回收
  • expireAfterAccess 多长时间没有被访问就回收

 

Guava Cache还支持基于引用级别的回收,这种回收策略是Java特有的,在Java的对象回收机制中,按对象的强弱可以分为强引用、软引用、弱引用、虚引用

强引用

强引用是我们最常用的引用,比如我们直接new一个对象,就是一个强引用

Map<String,String> map = new HashMap<String,String>();

强引用不会被自动回收,当内存不足时直接报内存溢出

软引用

软引用是一种不稳定的引用方式,如果一个对象具有软引用,当内存充足时,GC不会主动回收软引用对象,而当内存不足时软引用对象就会被回收

SoftReference<Object> softRef=new SoftReference<Object>(new Object()); // 软引用
Object object = softRef.get(); // 获取软引用

弱引用

弱引用是一种比软引用更不稳定的引用方式,因为无论内存是否充足,弱引用对象都有可能被回收

WeakReference<Object> weakRef = new WeakReference<Object>(new Object()); 
Object obj = weakRef.get(); // 获取弱引用

虚引用

虚引用这种引用方式就是形同虚设,因为如果一个对象仅持有虚引用,那么它就和没有任何引用一样。在实践中也几乎没有使用

在Guava中支持软/弱引用的回收方式

Cache<String,String> cache = CacheBuilder.newBuilder()
                .weakKeys() //使用弱引用存储键。当键没有其它(强或软)引用时,该缓存可能会被回收。
                .weakValues() //使用弱引用存储值。当值没有其它(强或软)引用时,该缓存可能会被回收。
                .softValues() //使用软引用存储值。当内存不足并且该值其它强引用引用时,该缓存就会被回收
                .build();

手动回收

上面讲的都是缓存自动回收的策略,我们也可以调用Guava Cache提供的方法来手动清除

可以删除单个key,也可以批量删除key,同时也可以清除整个缓存的数据(谨慎使用哦~)

    @Test
    public void invalidateTest(){
        Cache<String,String> cache = CacheBuilder.newBuilder().build();
        cache.put("a","1");
        cache.put("b","2");
        //从缓存中清除key为a的数据
        cache.invalidate("a");
        System.out.println(cache.getIfPresent("a"));

        cache.put("x","x1");
        cache.put("y","y1");
        System.out.println("x清除之前:"+ cache.getIfPresent("x"));
        System.out.println("y清除之前:"+ cache.getIfPresent("y"));
        List<String> list = Lists.newArrayList("x","y");
        //批量清除
        cache.invalidateAll(list);
        System.out.println("x清除之后:"+ cache.getIfPresent("x"));
        System.out.println("y清除之后:"+ cache.getIfPresent("y"));

        cache.put("y","y1");
        cache.put("z","z1");

        //清空缓存所有的数据
        cache.invalidateAll();

        System.out.println("全部清除后:" + cache.getIfPresent("y"));
        System.out.println("全部清除后:" + cache.getIfPresent("z"));

    }

运行结果:

null
x清除之前:x1
y清除之前:y1
x清除之后:null
y清除之后:null
全部清除后:null
全部清除后:null

数据清除监听器

可以给Cache中的对象加一个监听,当有对象被删除时会有事件通知

    @Test
    public void removeListenerTest() throws InterruptedException {
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .expireAfterWrite(Duration.ofSeconds(5))//5秒后自动过期
                //添加一个remove的监听器
                .removalListener(new RemovalListener<Object, Object>() {

                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println("[" +notification.getKey() + ":" + notification.getValue() + "] 被删除了");
                    }
                })
                .build();

        cache.put("a","1");
        Thread.sleep(2000);
        cache.put("b","2");
        cache.put("c","3");
        Thread.sleep(2000);
        cache.put("d","4");
        Thread.sleep(5000);
        cache.put("e","5");
        cache.invalidate("e");

    }

运行结果:

[a:1] 被删除了
[b:2] 被删除了
[c:3] 被删除了
[d:4] 被删除了

创建Cache时removalListener方法传入一个RemovalListener对象,重写onRemoval方法来进行数据清除事件的监听

从运行结果可以看出来,三种情况下的清除数据都会被监听

  • 超过容量,清除最早添加进缓存的数据
  • 超过设定的过期时间,缓存系统自动删除
  • 手动清除数据

统计信息

我们在使用缓存的时候,应该要关心缓存的命中率、加载数据时间等等信息,可以根据这些统计信息来对缓存进行优化调整,让缓存更好的为我们服务。在构建缓存对象时,可以开启统计信息,开启后会对缓存的操作进行统计

    @Test
    public void recordStatsTest(){
        Cache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .recordStats()
                .build();
        cache.put("a","1");
        cache.put("b","2");
        cache.put("c","3");
        cache.put("d","4");
        cache.put("e","5");
        cache.put("f","6");

        cache.getIfPresent("a");
        cache.getIfPresent("a");
        cache.getIfPresent("e");
        cache.getIfPresent("f");
        cache.getIfPresent("h");
        cache.getIfPresent("t");
        System.out.println(cache.stats());
    }

运行结果:

CacheStats{hitCount=2, missCount=4, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=3}

参考:

https://www.cnblogs.com/fnlingnzb-learner/p/11022152.html

https://blog.csdn.net/l1028386804/article/details/102764951

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CacheBuilder.newBuilder是Google Guava库中的一个方法,用于创建一个新的CacheBuilder实例。CacheBuilder是构建缓存的构建器,它提供了许多配置选项来自定义缓存的行为。使用CacheBuilder.newBuilder()方法可以创建一个默认的CacheBuilder实例,然后可以使用该实例上的其他方法来进行进一步的配置和构建。 例如,可以使用maximumSize方法设置缓存的最大容量,可以使用expireAfterAccess或expireAfterWrite方法设置缓存项的过期时间,可以使用removalListener方法设置缓存项被移除时的回调函数等等。 在引用中的代码示例中,CacheBuilder.newBuilder()方法被用于创建一个CacheBuilder实例,并在构建时通过链式调用方法设置了maximumSize选项。 注意,引用中提到的asMap视图上的方法不会自动加载缓存项,因此在需要自动加载缓存项的情况下,应该优先使用Cache.get(K, Callable<V>)方法,而不是Cache.asMap().putIfAbsent方法。 引用提到了Cache和LoadingCache接口,Cache是缓存的基本接口,LoadingCache是继承自Cache接口的接口,用于支持自动加载缓存项的缓存。在使用CacheBuilder.newBuilder()方法创建CacheBuilder实例时,可以通过build方法传入一个CacheLoader实例来实现自动加载缓存项的功能。 总之,CacheBuilder.newBuilder()方法是Google Guava库中用于创建CacheBuilder实例的方法,通过该实例可以进行缓存的配置和构建。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值