Java内存缓存神器:Caffeine(咖啡因)

文章目录

  • 一、Caffeine简介
  • 二、缓存加载
    • 1、手动加载
    • 2、自动加载
    • 3、手动异步加载(需要额外的包)
    • 4、自动异步加载
  • 三、缓存清理
    • 1、基于容量
    • 2、基于时间
    • 3、基于引用
  • 四、缓存移出
    • 1、手动移出
    • 2、移出监听器
  • 五、刷新缓存

一、Caffeine简介

官网:https://github.com/ben-manes/caffeine/wiki/Home-zh-CN

Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。

缓存和ConcurrentMap有点相似,但还是有所区别。最根本的区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存Cache 通常会被配置成自动驱逐缓存中元素,以限制其内存占用。在某些场景下,LoadingCache和AsyncLoadingCache 因为其自动加载缓存的能力将会变得非常实用。

Caffeine提供了灵活的构造器去创建一个拥有下列特性的缓存:
自动加载元素到缓存当中,异步加载的方式也可供选择
当达到最大容量的时候可以使用基于就近度和频率的算法进行基于容量的驱逐
将根据缓存中的元素上一次访问或者被修改的时间进行基于过期时间的驱逐
当向缓存中一个已经过时的元素进行访问的时候将会进行异步刷新
key将自动被弱引用所封装
value将自动被弱引用或者软引用所封装
驱逐(或移除)缓存中的元素时将会进行通知
写入传播到一个外部数据源当中
持续计算缓存的访问统计指标

为了提高集成度,扩展模块提供了JSR-107 JCache和Guava适配器。JSR-107规范了基于Java 6的API,在牺牲了功能和性能的代价下使代码更加规范。Guava的Cache是Caffeine的原型库并且Caffeine提供了适配器以供简单的迁移策略。

要使用Caffeine,首先要引入maven坐标:

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.6.2</version>
</dependency>

二、缓存加载

1、手动加载

Cache 接口提供了显式搜索查找、更新和移除缓存元素的能力。

推荐使用 cache.get(key, k -> value) 操作来在缓存中不存在该key对应的缓存元素的时候进行计算生成并直接写入至缓存内,而当该key对应的缓存元素存在的时候将会直接返回存在的缓存值。

一次 cache.put(key, value) 操作将会直接写入或者更新缓存里的缓存元素,在缓存中已经存在的该key对应缓存值都会直接被覆盖。

// 定义一个缓存, 过期时间10分钟,最大缓存数1w
Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build();// 查找一个缓存元素, 没有查找到的时候返回null
String value = cache.getIfPresent("key");
System.out.println(value); // null// 查找缓存,如果缓存不存在则生成缓存元素 并且塞入缓存,  如果无法生成则返回null
value = cache.get("key", k -> "new value");
System.out.println(value); // new value
System.out.println(cache.getIfPresent("key")); // new value// 添加或者更新一个缓存元素
cache.put("key", "new value2");
System.out.println(cache.getIfPresent("key")); // new value2// 移除一个缓存元素
cache.invalidate("key");
System.out.println(cache.getIfPresent("key")); // null

2、自动加载

一个LoadingCache是一个Cache 附加上 CacheLoader能力之后的缓存实现。

在build中,传入一个方法,该方法的参数就是key。如果调用get方法,key不存在时,会调用该方法生成该key的数据。

public static void main(String[] args) throws InterruptedException {LoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES).build(key -> createData(key));// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullString value = cache.get("key");System.out.println(value);// 批量查找缓存,如果缓存不存在则生成缓存元素Map<String, String> values = cache.getAll(Stream.of("key1", "key2").collect(Collectors.toList()));System.out.println(values);}private static String createData(String key) {System.out.println("createData + " + key);return key + "value";
}

3、手动异步加载(需要额外的包)

一个AsyncCache 是 Cache 的一个变体,AsyncCache提供了在 Executor上生成缓存元素并返回 CompletableFuture的能力。这给出了在当前流行的响应式编程模型中利用缓存的能力。

synchronous()方法给 Cache提供了阻塞直到异步缓存生成完毕的能力。

当然,也可以使用 AsyncCache.asMap()所暴露出来的ConcurrentMap的方法对缓存进行操作。

默认的线程池实现是 ForkJoinPool.commonPool() ,当然你也可以通过覆盖并实现 Caffeine.executor(Executor)方法来自定义你的线程池选择。

AsyncCache<Key, Graph> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).buildAsync();// 查找一个缓存元素, 没有查找到的时候返回null
CompletableFuture<Graph> graph = cache.getIfPresent(key);
// 查找缓存元素,如果不存在,则异步生成
graph = cache.get(key, k -> createExpensiveGraph(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.synchronous().invalidate(key);

4、自动异步加载

一个 AsyncLoadingCache是一个 AsyncCache 加上 AsyncCacheLoader能力的实现。

在需要同步的方式去生成缓存元素的时候,CacheLoader是合适的选择。而在异步生成缓存的场景下, AsyncCacheLoader则是更合适的选择并且它会返回一个 CompletableFuture

AsyncLoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES)// 你可以选择: 去异步的封装一段同步操作来生成缓存元素.buildAsync(key -> createData(key));// 你也可以选择: 构建一个异步缓存元素操作并返回一个future//.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));// 查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<String> value = cache.get("key");
// 批量查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Map<String, String>> graphs = cache.getAll(keys);

三、缓存清理

1、基于容量

LoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(10) // 基于容量,超过10个会基于最近最常使用算法 进行缓存清理.build(key -> createData(key));

2、基于时间

// 基于固定的过期时间驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));// 基于不同的过期驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfter(new Expiry<Key, Graph>() {public long expireAfterCreate(Key key, Graph graph, long currentTime) {// Use wall clock time, rather than nanotime, if from an external resourcelong seconds = graph.creationDate().plusHours(5).minus(System.currentTimeMillis(), MILLIS).toEpochSecond();return TimeUnit.SECONDS.toNanos(seconds);}public long expireAfterUpdate(Key key, Graph graph, long currentTime, long currentDuration) {return currentDuration;}public long expireAfterRead(Key key, Graph graph,long currentTime, long currentDuration) {return currentDuration;}}).build(key -> createExpensiveGraph(key));

Caffeine提供了三种方法进行基于时间的驱逐:

  • expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。在当被缓存的元素时被绑定在一个session上时,当session因为不活跃而使元素过期的情况下,这是理想的选择。
  • expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。
  • expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候,这是理想的选择。

3、基于引用

// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().weakKeys().weakValues().build(key -> createExpensiveGraph(key));// 当进行GC的时候进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().softValues().build(key -> createExpensiveGraph(key));

Caffeine 允许你配置你的缓存去让GC去帮助清理缓存当中的元素,其中key支持弱引用,而value则支持弱引用和软引用。记住 AsyncCache不支持软引用和弱引用。

Caffeine.weakKeys() 在保存key的时候将会进行弱引用。这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行key之间的比较。

Caffeine.weakValues()在保存value的时候将会使用弱引用。这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。

Caffeine.softValues()在保存value的时候将会使用软引用。为了相应内存的需要,在GC过程中被软引用的对象将会被通过LRU算法回收。由于使用软引用可能会影响整体性能,我们还是建议通过使用基于缓存容量的驱逐策略代替软引用的使用。同样的,使用 softValues() 将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。

四、缓存移出

1、手动移出

// 失效key
cache.invalidate(key)
// 批量失效key
cache.invalidateAll(keys)
// 失效所有的key
cache.invalidateAll()

2、移出监听器

可以为你的缓存通过Caffeine.removalListener(RemovalListener)方法定义一个移除监听器在一个元素被移除的时候进行相应的操作。这些操作是使用 Executor 异步执行的,其中默认的 Executor 实现是 ForkJoinPool.commonPool() 并且可以通过覆盖Caffeine.executor(Executor)方法自定义线程池的实现。

当移除之后的自定义操作必须要同步执行的时候,你需要使用 Caffeine.evictionListener(RemovalListener) 。这个监听器将在 RemovalCause.wasEvicted() 为 true 的时候被触发。为了移除操作能够明确生效, Cache.asMap() 提供了方法来执行原子操作。

Cache<Key, Graph> graphs = Caffeine.newBuilder().evictionListener((Key key, Graph graph, RemovalCause cause) ->System.out.printf("Key %s was evicted (%s)%n", key, cause)).removalListener((Key key, Graph graph, RemovalCause cause) ->System.out.printf("Key %s was removed (%s)%n", key, cause)).build();

五、刷新缓存

异步刷新缓存,重新加载。

LoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(10) .expireAfterWrite(10, TimeUnit.SECONDS).build(key -> createData(key));// 刷新
cache.refresh("key");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/195001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCL 点云加权均值收缩

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 受到之前Matlab 加权均值质心计算(WMN)的启发,我们在计算每个点的加权质心时可以很容易的发现,他们这些点会受到周围邻近点密度的影响,最后会收缩到某一个区域,那么这个区域也必定是我们比较感兴趣的一些点,…

conda使用——(待完善)

conda删除虚拟环境 conda env remove --name your_env_name Python Anaconda导出(export)环境到environment.yml文件conda create --prefix 命令安装虚拟环境到指定路径报错conda环境打包迁移及部署conda使用yaml创建虚拟环境conda创建新环境Linux查看当前Cuda&#xff08;CUDA…

《系统架构设计师教程(第2版)》第2章-计算机系统基础知识-02-计算软件

文章目录 1. 概述2. 操作系统2.1 操作系统的组成2.2 操作系统的作用2.3 操作系统的特征2.4 操作系统的分类2.4.1 批处理操作系统2.4.2 分时操作系统2.4.3 实时操作系统2.4.4 网络操作系统2.4.5 分布式操作系统2.4.6 微型计算机操作系统2.4.7 嵌入式操作系统3. 数据库3.1 关系型…

2023.12.4

race\牛客\多校\广西师范大学训练赛\雾锁山头山锁雾.cpp #include<bits/stdc.h> #include<iostream> #include<algorithm> #include<map> #include<set> #include<queue> #include<cstring> #include<math.h> #include<ma…

uniapp中进行地图定位

目录 一、创建map 二、data中声明变量 三、获取当前位置信息&#xff0c;进行定位 四、在methods中写移动图标获取地名地址的方法 五、最终展示效果 一、创建map <!-- 地图展示 --><view class"mymap"><!-- <view class"mymap__map"…

LangChain(0.0.340)官方文档五:Model

LangChain官网、LangChain官方文档 、langchain Github、langchain API文档、llm-universe 文章目录 一、Chat models1.1 Chat models简介1.2 Chat models的调用方式1.2.1 环境配置1.2.2 使用LCEL方式调用Chat models1.2.3 使用内置Chain调用Chat models 1.3 缓存1.3.1 内存缓存…

如何调用 API | 学习笔记

开发者学堂课程【阿里云 API 网关使用教程:如何调用 API】学习笔记&#xff0c;与课程紧密联系&#xff0c;让用户快速学习知识。 课程地址&#xff1a;阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 如何调用 API 调用 API 的三要素 要调用 API 需要三…

Oracle的回收站

Oracle的回收站 一、什么是oracle的回收站二、oracle的回收站相关操作1、设置Oracle回收站的状态2、回收站功能的使用3、查看回收站的状态和内容4、回收站具体应用案例&#xff1a; 一、什么是oracle的回收站 Oracle的回收站是一种功能&#xff0c;它允许用户在删除表中的数据…

Linux系统检查是以虚拟机还是物理机形式运行

平时工作中使用Linux大部分是通过ssh远程连接到服务器上,比如应用安装部署和系统维护等。那么,对于没有进过机房的人,如何知道自己使用的Linux服务器是虚拟机还是物理机呢? 1、dmidecode命令 dmidecode命令可以获取有关系统硬件的详细信息,包括制造商信息, 虚拟机通常会…

Hive 安装部署

文章目录 Hive 安装部署部署模式嵌入模式安装下载安装配置环境启动 Hive 客户端工具 本地模式安装下载安装 MySQL登录 MySQL安装 Hive Hive 命令行交互 Hive 安装部署 部署模式 Hive 有3种部署模式&#xff0c;分别是嵌入模式、本地模式和远程模式。关于这 3 种部署模式的具体…

【软件测试学习】—软件质量需求(四)

【软件测试学习】—软件质量需求&#xff08;四&#xff09; 1 软件质量需求的分类 软件质量需求用于确定测试目标。测试目标包括&#xff1a;功能、性能、界面、易用性、兼容性、安全性、可用性/可靠性、可维 护性、可扩展性等。功能以外统称非功能 2 功能  软件能做什么…

【JavaScript手撕代码】数组去重

利用Set function getUnRepeatArr(arr){return [...new Set(arr)] }利用filter function getUnRepeatArr(arr){return arr.filter((item, index) > {// 过滤的方法&#xff0c;如果说数组中最后一个元素的索引当前遍历到的元素的索引// 那么我们认为它是唯一的return arr.…

组网技术-交换机

交换机&#xff1a; 分类&#xff1a; 根据交换方式划分&#xff1a; 1.存储转发交换&#xff1a;交换机对输入的数据包先进行缓存、验证、碎片过滤&#xff0c;然后进行转发。 时延大&#xff0c;但是可以提供差错校验&#xff0c;并支持不同速度的输入、输出端口间的交换…

Python读取二进制文件:深入解析与技术实现

目录 一、引言 二、二进制文件的基础 1、二进制文件的组成 2、二进制文件的编码 三、Python读取二进制文件的方法 1、使用内置函数open() 2、使用numpy库 四、处理读取的二进制数据 1、解析数据 2. 转换数据类型 五、总结与展望 1、高效读取二进制文件 2、处理复杂…

ssm医药进出口交易系统源码和论文

ssm医药进出口交易系统源码和论文726 首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包罗软件架构…

电源自动切换初识

【前提&#xff1a;这里以一般的单片机产品为例&#xff0c;使用3.3V的供电系统&#xff0c;常见的USB供电、外接电源设配器供电和电池供电】 一、经典二极管切换电路 这是最简单的电源切换电路&#xff1a;二极管并联&#xff0c;理论上支持无数个电源切换&#xff0c;缺点是…

C++基础 -36- 模板之模板函数

模板函数格式 template <class T> void allexchange(T a,T b) {T c;c*a;*a*b;*bc; }模板函数可以增强函数的通用性 举例说明&#xff0c;使用一个模板函数实现了两个的函数的功能 #include "iostream"using namespace std;void myexchangeint(int* a,int* …

C语言图书管理系统的代码,包括图书录入、查询、插入、修改和删除功能。

以下是一个简单的C语言图书管理系统的代码&#xff0c;包括图书录入、查询、插入、修改和删除功能。 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_BOOKS 100 // 最大图书数量 // 图书结构体 typedef struct { int id; //…

linux作业管理_jobs

4.2 作业管理 是指控制当前正在运行的进程的行为&#xff0c;也称为进程控制。 是shell的一个特性&#xff0c;使用户能在多个独立进程间进行切换。 例如&#xff0c;用户可以挂起一个正在运行的进程&#xff0c;稍后再恢复其运行。当用户使用vim编辑一个文本文件&#xff0c…

【Python百宝箱】《AI之道:自动化机器学习与智能决策的终极指南》

代码之梦&#xff1a;探索Python AI库的奇妙世界 前言 在当今数字时代&#xff0c;人工智能和自动机器学习已成为推动科技创新的关键力量。本文深入探讨了多个强大的Python库&#xff0c;涵盖了从自动化模型选择、特征工程到模型解释性的全方位工具&#xff0c;为读者提供了丰…