前言

随着互联网的高速发展,市面上也出现了越来越多的网站和app。我们判断一个软件是否好用,用户体验就是一个重要的衡量标准。比如说我们经常用的微信,打开一个页面要十几秒,发个语音要几分钟对方才能收到。相信这样的软件大家肯定是都不愿意用的。软件要做到用户体验好,响应速度快,缓存就是必不可少的一个神器。缓存又分进程内缓存和分布式缓存两种:分布式缓存如redis、memcached等,还有本地(进程内)缓存如ehcache、GuavaCache、Caffeine等。说起Guava Cache,很多人都不会陌生,它是Google Guava工具包中的一个非常方便易用的本地化缓存实现,基于LRU算法实现,支持多种缓存过期策略。由于Guava的大量使用,Guava Cache也得到了大量的应用。但是,Guava Cache的性能一定是最好的吗?也许,曾经它的性能是非常不错的。正所谓长江后浪推前浪,前浪被拍在沙滩上。我们就来介绍一个比Guava Cache性能更高的缓存框架:Caffeine

Tips: Spring5(SpringBoot2)开始用Caffeine取代guava.详见官方信息SPR-13797
https://jira.spring.io/browse/SPR-13797

官方性能比较

以下测试都是基于jmh测试的,官网地址
测试为什么要基于jmh测试,可以参考知乎上R回答

在HotSpot VM上跑microbenchmark切记不要在main()里跑循环计时就完事。这是典型错误。重要的事情重复三遍:请用JMH,请用JMH,请用JMH。除非非常了解HotSpot的实现细节,在main里这样跑循环计时得到的结果其实对一般程序员来说根本没有任何意义,因为无法解释。

  • 8个线程读,100%的读操作

  • 6个线程读,2个线程写,也就是75%的读操作,25%的写操作。

  • 8个线程写,100%的写操作

对比结论

可以从数据看出来Caffeine的性能都比Guava要好。然后Caffeine的API的操作功能和Guava是基本保持一致的,并且  Caffeine为了兼容之前是Guava的用户,做了一个Guava的Adapter给大家使用也是十分的贴心。

如何使用

  • 在 pom.xml 中添加 caffeine 依赖

1<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
2<dependency>
3    <groupId>com.github.ben-manes.caffeine</groupId>
4    <artifactId>caffeine</artifactId>
5    <version>2.8.2</version>
6</dependency>

创建对象

1 Cache<String, Object> cache = Caffeine.newBuilder()
2                .initialCapacity(100)//初始大小
3                .maximumSize(200)//最大数量
4                .expireAfterWrite(3, TimeUnit.SECONDS)//过期时间
5                .build();
创建参数介绍
  • initialCapacity: 初始的缓存空间大小

  • maximumSize: 缓存的最大数量

  • maximumWeight: 缓存的最大权重

  • expireAfterAccess: 最后一次读或写操作后经过指定时间过期

  • expireAfterWrite: 最后一次写操作后经过指定时间过期

  • refreshAfterWrite: 创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存

  • weakKeys: 打开key的弱引用

  • weakValues:打开value的弱引用

  • softValues:打开value的软引用

  • recordStats:开发统计功能

注意:
expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。
maximumSize和maximumWeight不可以同时使用。

添加数据

Caffeine 为我们提供了手动、同步和异步这几种填充策略。
下面我们来演示下手动填充策略吧,其他几种如果大家感兴趣的可以去官网了解下

1  Cache<String, String> cache = Caffeine.newBuilder()
2                .build();
3        cache.put("java金融", "java金融");
4        System.out.println(cache.getIfPresent("java金融"));

自动添加(自定义添加函数)

 1  public static void main(String[] args) {2        Cache<String, String> cache = Caffeine.newBuilder()3                .build();4        // 1.如果缓存中能查到,则直接返回5        // 2.如果查不到,则从我们自定义的getValue方法获取数据,并加入到缓存中6        String val = cache.get("java金融", k -> getValue(k));7        System.out.println(val);8    }9    /**
10     * 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
11     * @param k
12     * @return
13     */
14    private static String getValue(String k) {
15        return k + ":value";
16    }

过期策略

Caffeine 为我们提供了三种过期策略
,分别是基于大小(size-based)、基于时间(time-based)、基于引用(reference-based)

基于大小(size-based)
 1      LoadingCache<String, String> cache = Caffeine.newBuilder()2                // 最大容量为13                .maximumSize(1)4                .build(k->getValue(k));5        cache.put("java金融1","java金融1");6        cache.put("java金融2","java金融2");7        cache.put("java金融3","java金融3");8        cache.cleanUp();9        System.out.println(cache.getIfPresent("java金融1"));
10        System.out.println(cache.getIfPresent("java金融2"));
11        System.out.println(cache.getIfPresent("java金融3"));

运行结果如下:淘汰了两个只剩下一个。

1null
2null
3java金融3
基于时间(time-based)

Caffeine提供了三种定时驱逐策略:

expireAfterWrite(long, TimeUnit)
  • 在最后一次写入缓存后开始计时,在指定的时间后过期。

 1  LoadingCache<String, String> cache =  Caffeine.newBuilder()2                // 最大容量为13                .maximumSize(1)4                .expireAfterWrite(3, TimeUnit.SECONDS)5                .build(k->getValue(k));6        cache.put("java金融","java金融");7        Thread.sleep(1*1000);8        System.out.println(cache.getIfPresent("java金融"));9        Thread.sleep(1*1000);
10        System.out.println(cache.getIfPresent("java金融"));
11        Thread.sleep(1*1000);
12        System.out.println(cache.getIfPresent("java金融"));

运行结果第三秒的时候取值为空。

1java金融
2java金融
3null
expireAfterAccess
  • 在最后一次读或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。

 1LoadingCache<String, String> cache =  Caffeine.newBuilder()2                // 最大容量为13                .maximumSize(1)4                .expireAfterAccess(3, TimeUnit.SECONDS)5                .build(k->getValue(k));6        cache.put("java金融","java金融");7        Thread.sleep(1*1000);8        System.out.println(cache.getIfPresent("java金融"));9        Thread.sleep(1*1000);
10        System.out.println(cache.getIfPresent("java金融"));
11        Thread.sleep(1*1000);
12        System.out.println(cache.getIfPresent("java金融"));
13        Thread.sleep(3001);
14        System.out.println(cache.getIfPresent("java金融"));

运行结果:读和写都没有的情况下,3秒后才过期,然后就输出了null。

1java金融
2java金融
3java金融
4null
expireAfter(Expiry)
  • 在expireAfter中需要自己实现Expiry接口,这个接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之后多久过期。注意这个是和expireAfterAccess、expireAfterAccess是互斥的。这里和expireAfterAccess、expireAfterAccess不同的是,需要你告诉缓存框架,他应该在具体的某个时间过期,获取具体的过期时间。

 1 LoadingCache<String, String> cache = Caffeine.newBuilder()2                // 最大容量为13                .maximumSize(1)4                .removalListener((key, value, cause) ->5                        System.out.println("key:" + key + ",value:" + value + ",删除原因:" + cause))6                .expireAfter(new Expiry<String, String>() {7                    @Override8                    public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {9                        return currentTime;
10                    }
11                    @Override
12                    public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
13                        return currentTime;
14                    }
15
16                    @Override
17                    public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
18                        return currentTime;
19                    }
20                })
21                .build(k -> getValue(k));

删除

  • 单个删除:Cache.invalidate(key)

  • 批量删除:Cache.invalidateAll(keys)

  • 删除所有缓存项:Cache.invalidateAll

总结

本文只是对Caffeine的一个简单使用的介绍,它还有很多不错的东西,比如缓存监控、事件监听、W-TinyLFU算法(高命中率、低内存占用)感兴趣的同学可以去官网查看。

参考

https://www.itcodemonkey.com/article/9498.html
https://juejin.im/post/5dede1f2518825121f699339
https://www.cnblogs.com/CrankZ/p/10889859.html
https://blog.csdn.net/hy245120020/article/details/78080686

https://www.zhihu.com/question/58735131/answer/307771944

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

本地缓存性能之王Caffeine相关推荐

  1. java cache定时过期,本地缓存高性能之王Caffeine

    前言 随着互联网的高速发展,市面上也出现了越来越多的网站和app.我们判断一个软件是否好用,用户体验就是一个重要的衡量标准.比如说我们经常用的微信,打开一个页面要十几秒,发个语音要几分钟对方才能收到. ...

  2. 本地缓存王者,Caffeine 保姆级教程!给力...

    缓存(Cache)在代码世界中无处不在.从底层的CPU多级缓存,到客户端的页面缓存,处处都存在着缓存的身影.缓存从本质上来说,是一种空间换时间的手段,通过对数据进行一定的空间安排,使得下次进行数据访问 ...

  3. 本地缓存框架:Caffeine Cache

    1. Caffine Cache 在算法上的优点-W-TinyLFU 说到优化,Caffine Cache到底优化了什么呢?我们刚提到过LRU,常见的缓存淘汰算法还有FIFO,LFU: 1.FIFO: ...

  4. Java高性能本地缓存框架Caffeine

    文章目录 Java高性能本地缓存框架Caffeine 如何使用 缓存加载 手动加载 自动加载 手动异步加载 自动异步加载 过期策略 基于大小 基于时间 基于引用 Caffeine.weakKeys() ...

  5. Caffeine Cache~高性能 Java 本地缓存之王

    前面刚说到Guava Cache,他的优点是封装了get,put操作:提供线程安全的缓存操作:提供过期策略:提供回收策略:缓存监控.当缓存的数据超过最大值时,使用LRU算法替换.这一篇我们将要谈到一个 ...

  6. Caffeine本地缓存详解

    一. 概述 Caffeine是一种高性能的缓存库,是基于Java 8的最佳(最优)缓存框架. 基于Google的Guava Cache,Caffeine提供一个性能卓越的本地缓存(local cach ...

  7. 解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

    大家好,又见面了. 本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面.如果感兴趣,欢迎关注以获取后续更新. 在前面的几篇文章中,我们一起聊了下本地 ...

  8. SpringBoot 集成 Caffeine(咖啡因)最优秀的本地缓存

    SpringBoot 集成 Caffeine(咖啡因)最优秀的本地缓存 本地缓存 为什么用Caffeine做本地缓存 SpringBoot2.0+如何集成 Caffeine 引入依赖 开启缓存 容器配 ...

  9. 分布式缓存与本地缓存的区别

    分布式缓存与本地缓存的区别 转载自:https://ost.51cto.com/posts/1002 缓存的概念: 在服务端中,缓存主要是指将数据库的数据加载到内存中,之后对该数据的访问都在内存中完成 ...

最新文章

  1. SDUT 2384 El Dorado
  2. 利用服务器修改服务器数据,用Jquery实现可编辑表格并用AJAX提交到服务器修改数据...
  3. 1.9 Java转换流:InputStreamReader和OutputStreamWriter
  4. boost::geometry::box_view用法的测试程序
  5. android list 比较,LinkedList 和 ArrayList 的区别
  6. [Java] 1010. Radix (25)-PAT甲级
  7. 关于Android中使用Enum的一点总结
  8. 宽字节与多字节之间的转换
  9. 中国程序员不得不使用的php框架,堪称不得已?
  10. VB实际读写INI文件
  11. 手机12306买卧铺下铺技巧_手机12306怎么买下铺
  12. 推荐一款可以在浏览器中运行的人脸识别库
  13. 方舟手游显示服务器超实,全渠道预约超过2200万,《方舟:生存进化手游》开启未来手游新篇章...
  14. Python中 {:.0f} 格式化输出,{0:^30}什么意思 . format(name))
  15. ARM USB蓝牙,Bluez 移植。
  16. 【Python刷题篇】——Python入门 011面向对象(二)
  17. u盘文件删去了怎么样找得回来
  18. BUUCTF:[UTCTF2020]sstv
  19. docker删除无用容器、镜像
  20. 圆角 border-radius

热门文章

  1. 小程序语音识别+php,微信小程序之语音识别(附小程序+服务器源码)
  2. python中使用html前端页面显示图像预测结果(改进)
  3. poj3686(最小权值完美匹配)
  4. hdu6989 (莫队+单调栈+ST表)
  5. python 搭建的http 动态服务器_Python 创建HTTP服务器的简单示例
  6. mysql cronjob 备份_mysql 数据备份 crontab
  7. 【网络流24题】解题报告:A、飞行员配对方案问题(最大流求二分图最大匹配)
  8. 计算机网络与通信pdf谢希仁_考研刷题资料谢希仁《计算机网络》(第7版)配套题库【考研真题精选(部分视频讲解)+章节题库】...
  9. python统计元素个数_python怎么统计列表中元素的个数
  10. 大一计算机考试题库window,2016年计算机考试Windows题库及答案