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

2023-12-04 10:36

本文主要是介绍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");

这篇关于Java内存缓存神器:Caffeine(咖啡因)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/453153

相关文章

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.