guava-2(cache缓存)

为什么用guava cache

有些情况我们不需要用redis,觉得大材小用了,二guava正好是本地缓存中比较优秀的一种,guava cache跟ConcurrentMap很想,都是通过键取对应的值,但是ConcurrentMap不能隐式的移除里面的数据,无法规定大小限制(也可以,需要自己实现逻辑)而guava cache已经帮我们实现了这些。

最简单demo

static Cache<String,Student> cache = CacheBuilder.newBuilder().build();
public static void main(String[] args) {
    
    
     cache.put("key1",new Student(18,"李静",2,2));
     cache.put("key2",new Student(7,"王萍",2,2));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
}

key不存在也想返回某个计算逻辑数据

第一种方式

使用LoadingCache接收,如果LoadingCache接受,则必须传CacheLoader参数,多了一个get方法,此get方法只传key就可以,不需要传callable,因为定义全局的时候已经设置了。

public static void main(String[] args) throws ExecutionException {
    
    
 //如果根据key取不到值,也需要返回一个数据
 LoadingCache<String,Student> cache = CacheBuilder.newBuilder().build(new CacheLoader<String, Student>() {
    
    
        @Override
        public Student load(String key) throws Exception {
    
    
            return new Student(0,"我是默认值",0,0);
        }
    });
    cache.put("key1",new Student(18,"李静",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    System.out.println(JSON.toJSONString(cache.get("key1")));
    System.out.println(JSON.toJSONString(cache.get("kkk")));
}

{“age”:18,“grade”:2,“name”:“李静”,“sex”:2}
{“age”:0,“grade”:0,“name”:“我是默认值”,“sex”:0}

第二种形式

可以看出第二种形式时,是对每个键都需要设置默认值,而第一中方式,我们可以全局设置,根据key执行某些算法逻辑返回需要的默认值。

public static void main(String[] args) throws ExecutionException {
    
    
 //如果根据key取不到值,也需要返回一个数据
    Cache<String,Student> cache = CacheBuilder.newBuilder().build();
    cache.put("key1",new Student(18,"李静",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.get("kkk",()->{
    
    
        return new Student(0,"我是kkk这个健的默认值",0,0);
    })));
}

{“age”:18,“grade”:2,“name”:“李静”,“sex”:2}
{“age”:0,“grade”:0,“name”:“我是kkk这个健的默认值”,“sex”:0}

限制缓存大小

缓存条数

如果大于缓存中的条数,最老的一条缓存会被回收掉

 public static void main(String[] args) throws ExecutionException {
    
    
  //如果根据key取不到值,也需要返回一个数据
    Cache<String,Student> cache = CacheBuilder.newBuilder().maximumSize(2).build();
    cache.put("key1",new Student(18,"李静",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    cache.put("key3",new Student(66,"萌萌牛",2,2));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
}

null
{“age”:7,“grade”:2,“name”:“王萍”,“sex”:2}
{“age”:66,“grade”:2,“name”:“萌萌牛”,“sex”:2}

缓存自定义数据,可以时value的大小

maximumWeight设置为200,会根据weigher的参数进行计算,如果和接近200就开始回收了,测试时临界是应该是200的一半左右,大于100后缓存就加不进去了。

maximumSize(long)、maximumWeight(long)是互斥的,只能二选

public static void main(String[] args) throws ExecutionException {
    
    
    //如果根据key取不到值,也需要返回一个数据
     //缓存回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。
     //缓存回收策略暂时不明
     Cache<String,Student> cache = CacheBuilder.newBuilder().maximumWeight(200).weigher((a,b)->{
    
    
         byte[] bytes = JSON.toJSONBytes(b);
         System.out.println(String.format("key [%s] 缓存[%s]大小为 %s",a,JSON.toJSONString(b),bytes.length));
         return bytes.length;
     }).build();
     cache.put("key1",new Student(18,"李静",2,2));
     cache.put("key2",new Student(7,"王萍",2,2));
     cache.put("key3",new Student(66,"萌萌牛",2,2));
     cache.put("key4",new Student(36,"萌萌虎",2,2));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
     System.out.println(JSON.toJSONString(cache.getIfPresent("key4")));
}
key [key1] 缓存[{
    
    "age":18,"grade":2,"name":"李静","sex":2}]大小为 44
key [key2] 缓存[{
    
    "age":7,"grade":2,"name":"王萍","sex":2}]大小为 43
key [key3] 缓存[{
    
    "age":66,"grade":2,"name":"萌萌牛","sex":2}]大小为 47
key [key4] 缓存[{
    
    "age":36,"grade":2,"name":"萌萌虎","sex":2}]大小为 47
null
null
{
    
    "age":66,"grade":2,"name":"萌萌牛","sex":2}
{
    
    "age":36,"grade":2,"name":"萌萌虎","sex":2}

maximumSize(long)只能限制条数。
maximumWeight(long)可以用来控制内存。比如我们代码中例子。

限制缓存时间

有两种形式

expireAfterWrite:从写入时间开始算

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    //如果根据key取不到值,也需要返回一个数据
    Cache<String,Student> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).maximumSize(20).build();
    cache.put("key1",new Student(18,"李静",2,2));
    cache.put("key2",new Student(7,"王萍",2,2));
    cache.put("key3",new Student(66,"萌萌牛",2,2));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
    TimeUnit.SECONDS.sleep(4);
    System.out.println("超过4秒后!!!!!!!!!!!!缓存时间为3秒");
    System.out.println(JSON.toJSONString(cache.getIfPresent("key1")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key2")));
    System.out.println(JSON.toJSONString(cache.getIfPresent("key3")));
}

expireAfterAccess:最后一次访问时间

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
	//如果根据key取不到值,也需要返回一个数据
	Cache<String,Student> cache = CacheBuilder.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS).maximumSize(20).build();
	cache.put("key1",new Student(18,"李静",2,2));
	cache.put("key2",new Student(7,"王萍",2,2));
	cache.put("key3",new Student(66,"萌萌牛",2,2));
	for (int i = 0; i < 10; i++) {
    
    
	    TimeUnit.SECONDS.sleep(1);
	    //模式key1永远访问,就是永不删除key缓存
	    cache.getIfPresent("key1");
	    System.out.println(String.format("[%d 秒后,缓存情况] %s",i+1,JSON.toJSONString(cache.asMap())));
	}
}

结果

[1 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2},"key2":{
    
    "age":7,"grade":2,"name":"王萍","sex":2},"key3":{
    
    "age":66,"grade":2,"name":"萌萌牛","sex":2}}
[2 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2},"key2":{
    
    "age":7,"grade":2,"name":"王萍","sex":2},"key3":{
    
    "age":66,"grade":2,"name":"萌萌牛","sex":2}}
[3 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[4 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[5 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[6 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[7 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[8 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[9 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}
[10 秒后,缓存情况] {
    
    "key1":{
    
    "age":18,"grade":2,"name":"李静","sex":2}}

可以看到,key1永远被访问,key1永远不会删除。

缓存回收

前面我们讲了如果缓存超过一定数量或者时间的时候,缓存会自动回收,那么我们如何手动回收缓存呢。

api 说明
Cache.invalidate(key) 根据指定键删除
Cache.invalidateAll(keys) 根据指定键集合删除
Cache.invalidateAll() 删除所有缓存

监听器

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
      //如果根据key取不到值,也需要返回一个数据
        Cache<String,Student> cache = CacheBuilder
                .newBuilder()
                .expireAfterAccess(3, TimeUnit.SECONDS)
                .removalListener(a->{
    
    
                    System.out.println(String.format("删除的key [%s] value [%s],删除原因 [%s]",a.getKey(),a.getValue(),a.getCause()));
                    System.out.println("删除的 key[" + a.getKey() + "],value[" + a.getValue() + "],remove reason[" + a.getCause() + "]");
                })
                .maximumSize(2).build();
        cache.put("key1",new Student(18,"李静",2,2));
        cache.invalidateAll();
        cache.put("key2",new Student(7,"王萍",2,2));
        cache.put("key3",new Student(66,"萌萌牛",2,2));
        cache.put("key3",new Student(1,"萌萌牛儿子",2,2));
        cache.put("key4",new Student(77,"萌萌虎",2,2));
        Thread.sleep(6000);
}

结果

删除的key [key1] value [com.example.demo.vo.Student@4361bd48],删除原因 [EXPLICIT]
删除的 key[key1],value[com.example.demo.vo.Student@4361bd48],remove reason[EXPLICIT]
删除的key [key3] value [com.example.demo.vo.Student@53bd815b],删除原因 [REPLACED]
删除的 key[key3],value[com.example.demo.vo.Student@53bd815b],remove reason[REPLACED]
删除的key [key2] value [com.example.demo.vo.Student@2a33fae0],删除原因 [SIZE]
删除的 key[key2],value[com.example.demo.vo.Student@2a33fae0],remove reason[SIZE]

key1:手动删除 EXPLICIT(显示)
key3:为替换,reason:REPLACED(替换)
key2:为超过最大条数删除 SIZE(大小)

现在不知为什么超时删除缓存并没有被监听到

统计分析

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
       //如果根据key取不到值,也需要返回一个数据
        Cache<String,Student> cache = CacheBuilder
                .newBuilder()
                .expireAfterWrite(11, TimeUnit.SECONDS)
                .recordStats()
                .removalListener(a->{
    
    
                    System.out.println(String.format("删除的key [%s] value [%s],删除原因 [%s]",a.getKey(),a.getValue(),a.getCause()));
                    System.out.println("删除的 key[" + a.getKey() + "],value[" + a.getValue() + "],remove reason[" + a.getCause() + "]");
                })
                .maximumSize(2).build();
        cache.put("key1",new Student(18,"李静",2,2));
        cache.invalidateAll();
        cache.put("key2",new Student(7,"王萍",2,2));
        cache.put("key3",new Student(66,"萌萌牛",2,2));
        cache.put("key3",new Student(1,"萌萌牛儿子",2,2));
        cache.put("key4",new Student(77,"萌萌虎",2,2));

        System.out.println(cache.getIfPresent("key1"));
        System.out.println(cache.getIfPresent("key4"));
        System.out.println(cache.stats());
}

结果:命中缓存的数据等等信息

删除的key [key1] value [com.example.demo.vo.Student@33833882],删除原因 [EXPLICIT]
删除的 key[key1],value[com.example.demo.vo.Student@33833882],remove reason[EXPLICIT]
删除的key [key3] value [com.example.demo.vo.Student@200a570f],删除原因 [REPLACED]
删除的 key[key3],value[com.example.demo.vo.Student@200a570f],remove reason[REPLACED]
删除的key [key2] value [com.example.demo.vo.Student@1e81f4dc],删除原因 [SIZE]
删除的 key[key2],value[com.example.demo.vo.Student@1e81f4dc],remove reason[SIZE]
null
com.example.demo.vo.Student@4d591d15
CacheStats{
    
    hitCount=1, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=1}

cache整合springboot

spring5开始不再支持guava cache,改用性能更高的Caffeine代替

猜你喜欢

转载自blog.csdn.net/qq_37904966/article/details/108169313