Caffeine本地缓存框架

Caffeine本地缓存框架

hi,我是阿昌,今天记录一下Java最强本地缓存Caffeine

1、缓存介绍

缓存(Cache),在软件无处不在。从底层CPU多级缓存,再到客户页面缓存,和服务器数据缓存,导出都存在着缓存的身影;

从本质上讲,缓存是一种空间换时间的手段,一种计算完后,使下次进行访问数据,不再重复处理,节省时间整体的处理时间;

针对Java而言,对应的常用缓存解决方案有很多,例如数据库缓存框架EhCache,分布式缓存Memcached/redis等,这些缓存方案实际上都是为了提升服务吞吐效率,避免持久层压力过大。

对于常见缓存类型而言,可以分为本地缓存以及分布式缓存两种,Caffeine就是一种优秀的本地缓存,而Redis可以用来做分布式缓存;

2、Caffeine介绍

Caffeine,基于Java8开发的一个高性能java本地缓存,它由Guava改进而来,在Spring5开始的默认缓存实现就将Caffeine代替原来的Google Guava,官方也称缓存命中率已经接近最优值;

它类似java中的ConcurrentMap,即支持并发,并且支持O(1)时间复杂度的数据存取,但又不完全相同,缓存通常会设置缓存自动过期策略,以限制优化缓存对内存占用;

它与ConcurrentMap的主要区别在于:

  1. ConcurrentMap会保留所有添加到其中的元素,直到它们被明确删除。
  2. Caffeine将通过给定的配置,自动移除“不常用”的数据,以保持内存的合理占用,即缓存自动过期策略。

因此,一种更好的理解方式是:Caffeine是一种带有存储和移除策略的Map

3、Caffeine基础

官方开发分档,以下基于maven包管理,使用Caffeine,需要在工程中引入如下依赖

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>3.0.5</version>
</dependency>
a、添加

Caffeine提供了四种缓存添加策略:

  1. 手动加载
  2. 自动加载
  3. 手动异步加载
  4. 自动异步加载
手动加载
Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).maximumSize(10_000).build();// 查找一个缓存元素, 没有查找到的时候返回null
String value = cache.getIfPresent("key");
// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回null
value = cache.get("key", k -> "value");
// 添加或者更新一个缓存元素
cache.put("key", "value");
// 移除一个缓存元素
cache.invalidate("key");

最普通的一种缓存,无需指定加载方式,需要手动调用put()进行加载。**put()**方法对于已存在的key将进行覆盖,这点和Map的表现是一致的。

在这里插入图片描述

在获取缓存值时,如果想要在缓存值不存在时,原子地将值写入缓存,则可以调用get(“key”, k -> “value”)方法,该方法将避免写入竞争。在多线程情况下,当使用get(key, k -> value)时,如果有另一个线程同时调用本方法进行竞争,则后一线程会被阻塞,直到前一线程更新缓存完成;

在这里插入图片描述

若另一线程调用**getIfPresent()**方法,则会立即返回null,不会被阻塞。

在这里插入图片描述

调用invalidate()方法,将手动移除缓存。


自动加载
LoadingCache<Key, Graph> cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回null
Graph graph = cache.get(key);
// 批量查找缓存,如果缓存不存在则生成缓存元素
Map<Key, Graph> graphs = cache.getAll(keys);

LoadingCache是一种自动加载的缓存。其和普通缓存不同的地方在于,当缓存不存在/缓存已过期时,若调用**get()**方法,则会自动调用CacheLoader.load()方法加载最新值。使用LoadingCache时,需要指定CacheLoader,并实现其中的load()方法供缓存缺失时自动加载。

在这里插入图片描述

调用**getAll()**方法将遍历所有的key调用get(),除非实现了CacheLoader.loadAll()方法。

在这里插入图片描述

在多线程情况下,当两个线程同时调用get(),则后一线程将被阻塞,直至前一线程更新缓存完成。


手动异步加载
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);

AsyncCache 是 Cache 的一个变体,响应结果均为 CompletableFuture。

默认情况下,缓存计算com.github.benmanes.caffeine.cache.Caffeine#getExecutor使用 ForkJoinPool.commonPool()[作为线程池,如果想要指定线程池,可以覆盖并实现 Caffeine.executor(Executor) 方法

在这里插入图片描述

synchronous() 提供了阻塞直到异步缓存生成完毕的能力,它将以 Cache 进行返回


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

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

与自动加载 LoadingCache 类似,AsyncLoadingCache 也需要指定 CacheLoader ,同时需要实现 load() 方法供供缓存缺失时的自动加载。

默认以 ForkJoinPool.commonPool()作为线程池来提交,如果想要指定线程池,可以覆盖并实现 AsyncCacheLoader.aysncLoad() 方法

在这里插入图片描述

b、驱逐策略

Caffeine 提供了三种驱逐策略,分别是

  • 基于容量
  • 基于时间
  • 基于引用
基于容量
// 基于缓存内的元素个数进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().maximumSize(10_000).build(key -> createExpensiveGraph(key));

如果缓存容量不希望超过某个特定的数量,可以使用Caffeine.maximumSize(long),来指定最大容量。缓存将会尝试通过基于就近度和频率的算法来驱逐掉不会再被使用到的元素。

缓存可能中的元素可能存在不同的“权重”,缓存中的元素可能有不同的内存占用–可以用Caffeine.weigher(Weigher) 方法来界定每个元素的权重并通过 Caffeine.maximumWeight(long)方法来界定缓存中元素的总权重来实现上述的场景。

// 基于缓存内元素权重进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().maximumWeight(10_000).weigher((Key key, Graph graph) -> graph.vertices().size()).build(key -> createExpensiveGraph(key));

在基于权重驱逐的策略下,一个缓存元素的权重计算是在其创建和更新时,此后其权重值都是静态存在的,在两个元素之间进行权重的比较时,并不会根据进行相对权重的比较。


基于时间
// 基于固定的过期时间驱逐策略
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): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候,这是理想的选择。

驱逐策略可以自由组合,在任意驱逐策略生效后,该缓存将会被清除

在写操作,和偶尔的读操作中将会进行周期性的过期事件的执行。过期事件的调度和触发将会在O(1)的时间复杂度内完成。

为了使过期更有效率,可以通过在你的Cache构造器中通过Scheduler接口和Caffeine.scheduler(Scheduler) 方法去指定一个调度线程代替在缓存活动中去对过期事件进行调度。使用Java 9以上版本的用户可以选择Scheduler.systemScheduler()利用系统范围内的调度线程。

当测试基于时间的驱逐策略的时候,不需要坐在板凳上等待现实时钟的转动。

使用Ticker接口和 Caffeine.ticker(Ticker)方法在你的Cache构造器中去指定一个时间源可以避免苦苦等待时钟转动的麻烦。Guava的测试库也提供了FakeTicker去达到同样的目的。


基于引用
// 当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之间的比较。

c、移除
手动显式移除

在任何时候,都可以手动去让某个缓存元素失效而不是只能等待其因为策略而被驱逐。

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

移除监听器

简单说就是在一个缓存被移除了后,异步回调移除监听器中的方法

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();

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

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

d、刷新
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(Duration.ofMinutes(5)).refreshAfterWrite(Duration.ofMinutes(1)).build(key -> createExpensiveGraph(key));

刷新和驱逐并不相同。可以通过LoadingCache.refresh(K)方法,异步为key对应的缓存元素刷新一个新的值。

与驱逐不同的是,在刷新的期间如果查询缓存元素,则还是返回旧值,直到该元素的刷新完毕后结束后才会返回刷新后的新值。

expireAfterWrite相反,refreshAfterWrite 将会使在写操作之后的一段时间后允许key对应的缓存元素进行刷新,但是只有在这个key被真正查询到的时候才会正式进行刷新操作。所以打个比方,可以在同一个缓存中同时用到 refreshAfterWriteexpireAfterWrite ,这样缓存元素的在被允许刷新的时候不会直接刷新使得过期时间被盲目重置。当一个元素在其被允许刷新但是没有被主动查询的时候,这个元素也会被视为过期。

一个CacheLoader可以通过覆盖重写 CacheLoader.reload(K, V) 方法使得在刷新中可以将旧值也参与到更新的过程中去,这也使得刷新操作显得更加智能。

LoadingCache.refresh(K)可用于显式刷新条目,并在请求时重复请求。返回的未来值可用于实现后备缓存,在后备缓存中,条目会被重新加载以从源获取最新值,但如果失败,则会查找并返回缓存值。

更新操作将会异步执行在一个Executor上。默认的线程池实现是ForkJoinPool.commonPool()当然也可以通过覆盖Caffeine.executor(Executor)方法自定义线程池的实现。

在刷新的过程中,如果抛出任何异常,都会使旧值被保留,并且异常将会被打印日志 (通过 System.Logger )并被吞食。

e、刷新
Cache<Key, Graph> graphs = Caffeine.newBuilder().maximumSize(10_000)//开启数据收集.recordStats().build();

通过使用Caffeine.recordStats()方法可以打开数据收集功能。

Cache.stats()方法将会返回一个CacheStats对象,其将会含有一些统计指标,比如:

  • hitRate(): 查询缓存的命中率
  • evictionCount(): 被驱逐的缓存数量
  • averageLoadPenalty(): 新值被载入的平均耗时

这些统计指标在缓存的调优中十分重要,强烈的建议性能指标严格的程序中去留意这些统计指标。

这些缓存统计指标可以被基于push/pull模式的报告系统进行集成。基于pull模式的系统可以通过调用Cache.stats() 方法获取当前缓存最新的统计快照。一个基于push的系统可以通过自定义一个StatsCounter对象达到在缓存操作发生时自动推送更新指标的目的。

f、清理

定时清理,在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而它将会在写操作之后进行少量的维护工作,在写操作较少的情况下,也偶尔会在读操作之后进行。

如果缓存吞吐量较高,那么不用去担心这个缓存的过期维护问题。但是如果缓存读写操作都很少,可以像下文所描述的方式额外通过一个线程去通过Cache.cleanUp() 方法在合适的时候触发清理操作。

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().scheduler(Scheduler.systemScheduler()).expireAfterWrite(10, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));

Scheduler可以提前触发过期元素清理移除。在过期事件之间进行调度,以期在短时间内最小化连续的批处理操作的数量。这里的调度是尽可能做到合理,并不能保证在一个元素过期的时候就将其清除。

Java 9以上的用户可以通过Scheduler.systemScheduler()来利用专用的系统范围内的调度线程。

Cache<Key, Graph> graphs = Caffeine.newBuilder().weakValues().build();Cleaner cleaner = Cleaner.create();
cleaner.register(graph, graphs::cleanUp);
graphs.put(key, graph);

Java 9以上的也可以通过Cleaner去触发移除关于基于引用的元素(在使用了 weakKeys, weakValues, 或者 softValues的情况下)。

只要将key或者缓存的元素value注册到Cleaner上,就可以在程序运行中调用Cache.cleanUp()方法触发缓存的维护工作。

4、本地变量缓存通知

本地缓存实现,基于redisson 事件通知,进行分布式过期处理

@Slf4j
@Component
@RequiredArgsConstructor
public class LocalCacheService {private final RedissonClient redissonClient;public static String topicKey = "env_cache_key";//创建guava cachepublic final LocalCache<String, String> userEnvcache = new LocalCache<>(topicKey,500,2000,30);public RedissonDistributeEvent redissonDistributeEvent;/*** 开启监听*/@PostConstructvoid init() {redissonDistributeEvent = new RedissonDistributeEvent(topicKey,redissonClient,new MessageListener<String>() {@Overridepublic void onMessage(CharSequence topic, String msg) {consume(msg);}});}public void produceInvalidateMsg(String key) {try {if (redissonDistributeEvent != null) {redissonDistributeEvent.publishAsync(key);}} catch (Exception e) {log.error("produceInvalidateMsg failed:{}", e.getMessage(), e);}}void consume(String msg) {expireUserEnv(msg);}public void expireUserEnv(String key) {userEnvcache.invalidate(key);}public String getUserEnvStr(String key, Function<String, String> function) {return userEnvcache.get(key, () -> function.apply(key));}}

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

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

相关文章

HBuilder X 中Vue.js基础使用2(三)

一、条件渲染 1、条件判断 v-if &#xff1a; 表达式返回真值时才被渲染 v-else &#xff1a;表达式返回为假时不被渲染 2、 分支条件判断 v-else-if &#xff1a;使用v-if , v-else-if 和 v-else 来表示其他的条件分支 3、显示隐藏 v-show v-show true 把节点显示 …

PortQry下载安装使用教程(超详细),Windows测试UDP端口

《网络安全自学教程》 PortQry是微软官方提供的一款TCP/IP连接「排障工具」&#xff0c;用来「检查」TCP/UDP「端口状态」。 平时检查端口状态&#xff0c;最常用的是telnet&#xff0c;但它是基于TCP协议的&#xff0c;无法检测「UDP端口」&#xff0c;这篇文章教大家如何在W…

Axure随机验证码高级交互

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;字母数字随机验证码高级交互 主要内容&#xff1a;4位字母数字随机验证码生成、错误提示与State状态同步 应用场景&#xff1a;登录验证码、其他类…

面试宝典(五):用三个线程按顺序循环打印123三个数字,比如123123123

要使用三个线程按顺序循环打印123三个数字&#xff0c;势必要控制线程的执行顺序&#xff0c;可以使用java.util.concurrent包中的Semaphore类来控制线程的执行顺序。 代码示例 import java.util.concurrent.Semaphore;public class SequentialPrinting123 {private static Se…

leetcode:34. 在排序数组中查找元素的第一个和最后一个位置(python3解法)

#1024程序员节 | 征文# 难度&#xff1a;中等 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(lo…

初识算法 · 前缀和(1)

目录 前言&#xff1a; 一维数组的前缀和 题目解析 算法原理 算法编写 二维数组的前缀和 题目解析 算法原理 算法编写 前言&#xff1a; ​本文的主题是前缀和&#xff0c;通过两道题目讲解&#xff0c;一道是一维数组的模板&#xff0c;一道是二维数组的模板。 链接…

【WebGIS实例】(18)MapboxGL 绘制矢量——线、面

前言 Mapbox GL JS 版本&#xff1a;3.6.0 该博客仅供学习参考&#xff0c;如果您是计划在实际项目中实现该功能&#xff0c;也推荐您直接使用已有的功能库&#xff1a; 官方案例&#xff1a;Draw a polygon and calculate its areamapbox-gl-draw&#xff1a;mapbox/mapbox-g…

基于Django+python的酒店客房入侵检测系统设计与实现

项目运行 需要先安装Python的相关依赖&#xff1a;pymysql&#xff0c;Django3.2.8&#xff0c;pillow 使用pip install 安装 第一步&#xff1a;创建数据库 第二步&#xff1a;执行SQL语句&#xff0c;.sql文件&#xff0c;运行该文件中的SQL语句 第三步&#xff1a;修改源…

HTTPS讲解

前瞻 HTTP与HTTPS的关系 HTTPS也是一个在应用层的协议&#xff0c;是在HTTP协议基础上的一个加密解密层 明文 密文 秘钥 明文->秘钥 加密 秘钥->明文 解密 例如:明文为7 秘钥为2 7^21015&#xff1b; 5就是密文例子: 因为http的内容是明文传输的&#xff0c;明文…

危险物品图像分割系统:一键训练

危险物品图像分割系统源码&#xff06;数据集分享 [yolov8-seg-GFPN&#xff06;yolov8-seg-CSwinTransformer等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global…

LabVIEW共享变量通信故障

问题概述&#xff1a; 在LabVIEW项目中&#xff0c;使用IO服务器创建共享变量&#xff0c;并通过LabVIEW作为从站进行数据通信。通讯在最初运行时正常&#xff0c;但在经过一段时间或几个小时后&#xff0c;VI前面板出现错误输出&#xff0c;导致数据传输失败。虽然“分布式系统…

折扣影票接口对接渠道如何选择?

选择折扣影票接口对接渠道需要综合多方面因素考虑&#xff0c;以下是一些建议&#xff1a; 1.合法性和合规性&#xff1a; 确认供应商资质&#xff1a;优先选择具有相关票务经营资质的渠道。比如一些大型的在线票务平台&#xff0c;它们通常经过官方认证和监管&#xff0c;在…

[JAVAEE] 多线程的案例(二) - 阻塞队列 生产者消费者模型

目录 一. 什么是阻塞队列 二. java中的阻塞队列 三. 生产者消费者模型 3.1 生产者消费者模型与阻塞队列密不可分的关系 3.2 阻塞队列在生产者消费者模型的作用 a. 解耦合 b. 削峰填谷 四. 模拟实现阻塞队列 4.1 实现put方法 4.2 实现take方法 4.3 生产者消费者模型​…

了解C# 程序结构

本节我们将学习 C# 编程语言的结构&#xff0c;为了让大家能够对 C# 程序结构有个更好的理解&#xff0c;我们会先演示一个最小的、最简单的 C# 程序结构&#xff0c;以便作为接下来的章节的参考。 C# Hello World 实例 一个 C# 程序主要包括以下部分&#xff1a; 命名空间声明…

08 实战:色彩空间展示(本程序以视频为主)

程序效果如下: 我在这里讲解RGB和YCbCr的原理: 一、RGB颜色空间 1.1 基本概念 RGB颜色空间是一种最基础和常用的颜色表示方式,它基于人眼感知色彩的三原色原理。RGB分别代表: R(Red):红色G(Green):绿色B(Blue):蓝色通过这三种基本颜色的不同组合,可以产生人眼…

Promise、async、await 、异步生成器的错误处理方案

1、Promise.all 的错误处理 Promise.all 方法接受一个 Promise 数组&#xff0c;并返回所有解析 Promise 的结果数组&#xff1a; const promise1 Promise.resolve("one"); const promise2 Promise.resolve("two");Promise.all([promise1, promise2]).…

基于人体姿势博文文章

MATLAB运动目标检测系统应用背景 运动目标的定位跟踪&#xff0c;检测识别&#xff0c;运动分析在图像压缩、运动分析、交通检测&#xff0c;智能监控等方面有主要的应用。 首先&#xff0c;在图像压缩中&#xff0c;运动目标检测技术可以在背景区域中将前景区域提取分割出来…

91.【C语言】数据结构之单向链表的查找,中间插入和删除,销毁

目录 1.链表的查找函数 2.链表的修改函数 3.链表的中间插入函数 1.在pos之前插入:SLTInsertBefore函数 1.借助头指针pphead 示意图 代码示例(写入SList.c) 头文件添加SLTInsertbefore的声明 main.c的部分代码改为 1.测试中间插入 2.测试头部插入 3.测试pos为NULL的…

机器视觉:9点标定的原理与实现

一、什么是标定 标定就是将机器视觉处理得到的像素坐标转换成实际项目中使用到的毫米坐标。简单说即使看看实际单位距离内有几个像素&#xff0c;如下图所示&#xff0c;10mm的距离内有222个像素&#xff0c;那像素坐标和实际的毫米坐标就有个比例关系了。 二、九点标定 9点标…

API网关的作用--为什么微服务需要一个API网关?

微服务网关核心作用就是协议转换、安全隔离和流量控制 微服务架构中&#xff0c;API网关作为系统的入口点&#xff0c;可以统一处理所有客户端请求。 1&#xff09;协议转换&#xff1a;它能够支持多种通信协议&#xff08;如HTTP、gRPC等&#xff09;之间的相互转换&#xff…