本地缓存解决方案Caffeine | Spring Cloud 38

news/2024/4/19 15:21:41/文章来源:https://blog.csdn.net/ctwy291314/article/details/130357265

一、Caffeine简介

Caffeine是一款高性能、最优缓存库。Caffeine是受Google guava启发的本地缓存(青出于蓝而胜于蓝),在Cafeine的改进设计中借鉴了 Guava 缓存和 ConcurrentLinkedHashMapGuava缓存可以参考上篇:本地缓存解决方案GuavaCache | Spring Cloud 37,就和MybatisMybatis Plus一样。Caffeine也是Spring5.X后使用的缓存框架,作为Spring推荐的缓存框架我们有必要了解一下。

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

以下为截取官网的部分测试结果:
生成计算测试数据
读测试数据

详细基准测试结果请见:https://github.com/ben-manes/caffeine/wiki/Benchmarks-zh-CN

二、应用及特性说明

2.1 Maven依赖

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

注意Caffeine的版本需要和JDK版本对应:

  • 2.X版本对应JDK8
  • 3.X版本对应的JDK版本为11

2.2 缓存添加

Caffeine提供了四种缓存添加策略:手动加载,自动加载,手动异步加载和自动异步加载。

2.2.1 手动加载

private static void manual() {// 构建caffeine的缓存对象,并指定在写入后的10分钟内有效,且最大允许写入的条目数为10000Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build();String key = "hello";// 查找某个缓存元素,若找不到则返回nullString str = cache.getIfPresent(key);System.out.println("cache.getIfPresent(key) ---> " + str);// 查找某个缓存元素,若找不到则调用函数生成,如无法生成则返回nullstr = cache.get(key, k -> create(key));System.out.println("cache.get(key, k -> create(key)) ---> " + str);// 添加或者更新一个缓存元素cache.put(key, str);System.out.println("cache.put(key, str) ---> " + cache.getIfPresent(key));// 移除一个缓存元素cache.invalidate(key);System.out.println("cache.invalidate(key) ---> " + cache.getIfPresent(key));
}private static String create(Object key) {return key + " world";
}

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

缓存元素可以通过调用 cache.put(key, value)方法被加入到缓存当中。如果缓存中指定的key已经存在对应的缓存元素的话,那么先前的缓存的元素将会被直接覆盖掉。因此,通过 cache.get(key, k -> value) 的方式将要缓存的元素通过原子计算的方式 插入到缓存中,以避免和其他写入进行竞争。值得注意的是,当缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,cache.get 也许会返回 null
当然,也可以使用Cache.asMap()所暴露出来的ConcurrentMap的方法对缓存进行操作。

2.2.2 自动加载

public static void loading() {LoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES).build(key -> create(key)); // 当调用get或者getAll时,若找不到缓存元素,则会统一调用create(key)生成String key = "hello";String str = cache.get(key);System.out.println("cache.get(key) ---> " + str);List<String> keys = Arrays.asList("a", "b", "c", "d", "e");// 批量查找缓存元素,如果缓存不存在则生成缓存元素Map<String, String> maps = cache.getAll(keys);System.out.println("cache.getAll(keys) ---> " + maps);
}private static String create(Object key) {return key + " world";
}

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

通过 getAll可以达到批量查找缓存的目的。 默认情况下,在getAll 方法中,将会对每个不存在对应缓存的key调用一次 CacheLoader.load 来生成缓存元素。 在批量检索比单个查找更有效率的场景下,你可以覆盖并开发CacheLoader.loadAll 方法来使你的缓存更有效率。

值得注意的是,你可以通过实现一个 CacheLoader.loadAll并在其中为没有在参数中请求的key也生成对应的缓存元素。打个比方,如果对应某个key生成的缓存元素与包含这个key的一组集合剩余的key所对应的元素一致,那么在loadAll中也可以同时加载剩下的key对应的元素到缓存当中。

2.2.3 手动异步加载

private static void asynchronous() {AsyncCache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).buildAsync();String key = "Hello";// 查找某个缓存元素,若找不到则返回nullCompletableFuture<String> value = cache.getIfPresent(key);// 查找某个缓存元素,若不存在则异步调用create方法生成value = cache.get(key, k -> create(key));// 添加或者更新一个缓存元素cache.put(key, value);// 移除一个缓存元素cache.synchronous().invalidate(key);
}private static String create(Object key) {return key + " world";
}

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

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

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

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

2.2.4 自动异步加载

private static void asynchronouslyLoading() {AsyncLoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES)// 异步构建一个同步的调用方法create(key).buildAsync(key -> create(key));// 也可以使用下面的方式来异步构建缓存,并返回一个future// .buildAsync((key, executor) -> createAsync(key, executor));String key = "Hello";// 查找某个缓存元素,若找不到则会异步生成。CompletableFuture<String> value = cache.get(key);List<String> keys = Arrays.asList("a", "b", "c", "d", "e");// 批量查找某些缓存元素,若找不到则会异步生成。CompletableFuture<Map<String, String>> values = cache.getAll(keys);
}private static String create(Object key) {return key + " world";
}

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

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

通过 getAll可以达到批量查找缓存的目的。 默认情况下,在getAll 方法中,将会对每个不存在对应缓存的key调用一次 AsyncCacheLoader.asyncLoad 来生成缓存元素。 在批量检索比单个查找更有效率的场景下,你可以覆盖并开发AsyncCacheLoader.asyncLoadAll 方法来使你的缓存更有效率。

值得注意的是,你可以通过实现一个 AsyncCacheLoader.asyncLoadAll并在其中为没有在参数中请求的key也生成对应的缓存元素。打个比方,如果对应某个key生成的缓存元素与包含这个key的一组集合剩余的key所对应的元素一致,那么在asyncLoadAll中也可以同时加载剩下的key对应的元素到缓存当中。

private static void timeBased() {// 自上一次写入或者读取缓存开始,在经过指定时间之后过期。LoadingCache<String, String> fixedAccess = Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(key -> create(key));// 自缓存生成后,经过指定时间或者一次替换值之后过期。LoadingCache<String, String> fixedWrite = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(key -> create(key));// 自定义缓存过期策略,可以在创建时,写入后、读取时。LoadingCache<String, String> varying = Caffeine.newBuilder().expireAfter(new Expiry<String, String>() {public long expireAfterCreate(String key, String value, long currentTime) {return currentTime;}public long expireAfterUpdate(String key, String value, long currentTime, long currentDuration) {return currentDuration;}public long expireAfterRead(String key, String value, long currentTime, long currentDuration) {return currentDuration;}}).build(key -> create(key));
}

2.3 驱逐策略

Caffeine 提供了三种驱逐策略,分别是基于容量,基于时间和基于引用三种类型。

本文重点描述时间驱逐策略,其他驱逐策略请见官网:https://github.com/ben-manes/caffeine/wiki/Eviction-zh-CN

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

  • expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。

    在当被缓存的元素时被绑定在一个session上时,当session因为不活跃而使元素过期的情况下,这是理想的选择。

  • expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。

    在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。

  • expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。

    当被缓存的元素过期时间受到外部资源影响的时候,这是理想的选择。

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

在默认情况下,当一个缓存元素过期的时候,Caffeine 不会自动立即将其清理和驱逐。而它将会在写操作之后进行少量的维护工作,在写操作较少的情况下,也偶尔会在读操作之后进行。如果你的缓存吞吐量较高,那么你不用去担心你的缓存的过期维护问题。但是如果你的缓存读写操作都很少,可以额外通过一个线程使用 Cache.cleanUp() 方法在合适的时候触发清理操作

private static void customTime() throws InterruptedException {LoadingCache<String, String> cache = Caffeine.newBuilder().scheduler(Scheduler.forScheduledExecutorService(Executors.newScheduledThreadPool(1))).evictionListener((String key, String value, RemovalCause cause) -> {log.info("EvictionListener key {} was removed {}", key, cause);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}}).removalListener((String key, String value, RemovalCause cause) -> {log.info("RemovalListener key {} was removed {}", key, cause);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}}).expireAfter(new Expiry<String, String>() {@Overridepublic long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {// 这里的currentTime由Ticker提供,默认情况下与系统时间无关,单位为纳秒log.info("expireAfterCreate----key:{},value:{},currentTime:{}", key, value, currentTime);return TimeUnit.SECONDS.toNanos(10);}@Overridepublic long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {// 这里的currentTime由Ticker提供,默认情况下与系统时间无关,单位为纳秒log.info("expireAfterUpdate----key:{},value:{},currentTime:{},currentDuration:{}", key, value, currentTime, currentDuration);return TimeUnit.SECONDS.toNanos(5);}@Overridepublic long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {// 这里的currentTime由Ticker提供,默认情况下与系统时间无关,单位为纳秒log.info("expireAfterRead----key:{},value:{},currentTime:{},currentDuration:{}", key, value, currentTime, currentDuration);return TimeUnit.SECONDS.toNanos(5);}}).build(key -> create(key));String one = cache.get("one");log.info("第一次获取one:{}", one);String two = cache.get("two");log.info("第一次获取two:{}", two);cache.put("one", one + "_new");log.info("---------------开始休眠5秒---------------");Thread.sleep(5000);log.info("---------------结束休眠5秒---------------");one = cache.get("one");log.info("第二次获取one:{}", one);two = cache.get("two");log.info("第二次获取two:{}", two);log.info("---------------开始休眠10秒---------------");Thread.sleep(10000);log.info("---------------结束休眠10秒---------------");one = cache.get("one");log.info("第三次获取one:{}", one);two = cache.get("two");log.info("第三次获取two:{}", two);Thread.sleep(20000);//cache.cleanUp();Thread.sleep(20000);
}private static String create(String key) {log.info("自动加载数据:{}", key);return UUID.randomUUID().toString();
}

2.4 缓存移除

  • 驱逐 缓存元素因为策略被移除
  • 失效 缓存元素被手动移除
  • 移除 由于驱逐或者失效而最终导致的结果

2.4.1 显式移除

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

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

2.4.2 移除监听器

Cache<Key, Graph> graphs = Caffeine.newBuilder().evictionListener((String key, String value, RemovalCause cause) ->log.info("EvictionListener key {} was removed {}", key, cause)).removalListener((String key, String value, RemovalCause cause) ->log.info("RemovalListener key {} was removed {}", key, cause)).build();

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

当移除之后的自定义操作必须要同步执行的时候,你需要使用 Caffeine.evictionListener(RemovalListener) 。这个监听器将在 RemovalCause.wasEvicted()true 的时候被触发。

使用Caffeine.evictionListener(RemovalListener)监听器时,因是同步执行故对缓存添加操作造成影响。详见2.3中代码说明和示例。

2.5 缓存刷新

private static String create(String key) {log.info("自动加载数据:{}", key);return UUID.randomUUID().toString();
}private static void refresh() throws InterruptedException {LoadingCache<String, String> cache = Caffeine.newBuilder().scheduler(Scheduler.forScheduledExecutorService(Executors.newScheduledThreadPool(1))).evictionListener((String key, String value, RemovalCause cause) -> {log.info("EvictionListener key {} was removed {}", key, cause);}).removalListener((String key, String value, RemovalCause cause) -> {log.info("RemovalListener key {} was removed {}", key, cause);}).expireAfterWrite(10, TimeUnit.SECONDS).build(key -> create(key));String one = cache.get("one");log.info("第一次获取one:{}", one);String two = cache.get("two");log.info("第一次获取two:{}", two);log.info("---------------开始休眠30秒---------------");Thread.sleep(30000);log.info("---------------结束休眠30秒---------------");one = cache.get("one");log.info("第二次获取one:{}", one);two = cache.get("two");log.info("第二次获取two:{}", two);
}

refresh只有在LoadingCache或者AsyncLoadingCache时才能使用,与驱逐不同之处,
异步为key对应的缓存元素刷新一个新的值。与驱逐不同的是,在刷新的时候如果查询缓存元素,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。

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

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

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

2.6 Write

CacheWriter允许缓存充当一个底层资源的代理,当与CacheLoader结合使用时,所有对缓存的读写操作都可以通过Writer进行传播。Writer可以把操作缓存和操作外部资源扩展成一个同步的原子性操作。并且在缓存写入完成之前,它将会阻塞后续的更新缓存操作,但是读取(get)将直接返回原有的值。如果写入程序失败,那么原有的keyvalue的映射将保持不变,如果出现异常将直接抛给调用者。

CacheWriter可以同步的监听到缓存的创建、变更和删除操作。

加载(如LoadingCache.get)、重新加载(如LoadingCache.refresh)和计算(如Map.computeIfPresent)的操作不会被CacheWriter监听到。

CacheWriter不能与weakKeysAsyncLoadingCache结合使用。且不支持Caffeine 3.X版本。

2.6.1 可能的用例(Possible Use-Cases)

CacheWriter是复杂工作流的扩展点,需要外部资源来观察给定Key的更改顺序。Caffeine 支持这些用法,但不是内置的。

2.6.2 写模式(Write Modes)

CacheWriter可以用来实现一个直接写(write-through)或回写(write-back)缓存的操作。

  • write-through式缓存中,操作是同步执行的,只有写成功了才会去更新缓存。这避免了同时去更新资源和缓存的条件竞争。

  • write-back式缓存中,对外部资源的操作是在缓存更新后异步执行的。这样可以提高写入的吞吐量,避免数据不一致的风险,比如如果写入失败,则在缓存中保留无效的状态。这种方法可能有助于延迟写操作,直到指定的时间,限制写速率或批写操作。

通过对write-back进行扩展,我们可以实现以下特性:

  • 批处理和合并操作
  • 将操作延迟到一个时间窗口
  • 如果超过阈值大小,则在定期刷新之前执行批处理
  • 如果操作尚未刷新,则从后写缓冲区加载
  • 根据外部资源的特性处理重试、速率限制和并发

2.6.3 分层(Layering)

CacheWriter可能用来集成多个缓存进而实现多级缓存。

多级缓存的加载和写入可以使用系统外部高速缓存。这允许缓存使用一个小并且快速的缓存去调用一个大的并且速度相对慢一点的缓存。典型的堆外缓存、基于文件的缓存和远程缓存。

受害者缓存是一个多级缓存的变体,其中被删除的数据被写入二级缓存。这个delete(K, V, RemovalCause) 方法允许检查为什么该数据被删除,并作出相应的操作。

2.6.4 同步监听器(Synchronous Listeners)

同步监听器会接收一个key在缓存中的进行了那些操作的通知。监听器可以阻止缓存操作,也可以将事件排队以异步的方式执行。这种类型的监听器最常用于复制或构建分布式缓存。

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

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

相关文章

【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(九)Nacos+Sentinel+Seata

NacosSentinelSeata 9/9 1、SpringCloud Alibaba简介1.1 主要功能1.2 具体组件 2、SpringCloud Alibaba Nacos服务注册和配置中心2.1 Nacos介绍2.2 Nacos下载安装2.3 使用Nacos作为注册中心2.3.1 在父工程的pom文件中引入springcloudalibaba依赖2.3.2 创建cloudalibaba-provide…

适合学生党的蓝牙耳机品牌有哪些?性价比高的无线耳机推荐

相较于有线耳机&#xff0c;蓝牙耳机的受欢迎程度可谓是越来越高&#xff0c;当然&#xff0c;这也离不开部分手机取消耳机孔的设计。最近看到很多网友问&#xff0c;适合学生党的蓝牙耳机品牌有哪些&#xff1f;针对这个问题&#xff0c;我来给大家推荐几款性价比高的无线耳机…

static_cast、dynamic_cast和reinterpret_cast区别和联系

其实网上相关的资料不少&#xff0c;但是能够说清楚明白这个问题的也不多。 于是&#xff0c;我尝试着问了一下AI&#xff0c;感觉回答还可以&#xff0c;但是需要更多的资料验证。 让我们先看看AI是怎么回答这个问题的。 static_cast、dynamic_cast和reinterpret_cast都是C中…

视频音频提取器推荐:快速提取视频中的音频!

视频中的音频可以用于很多用途&#xff0c;比如制作配乐、音频剪辑等。但是&#xff0c;许多人并不知道如何将视频中的音频提取出来。如果您也是这样的情况&#xff0c;那么本文为您介绍一个简单易用的视频音频提取器&#xff1a;。 它是一个免费的在线工具&#xff0c;可以帮…

如何在Web上实现激光点云数据在线浏览和展示?

无人机激光雷达测量是一项综合性较强的应用系统&#xff0c;具有数据精度高、层次细节丰富、全天候作业等优势&#xff0c;能够精确测量三维现实世界&#xff0c;为各个行业提供了丰富有效的数据信息。但无人机激光雷达测量产生的点云数据需要占用大量的存储空间&#xff0c;甚…

DataGridView 真·列头不高亮 真·列头合并

高亮BUG VB.Net&#xff0c;在 .NET Framework 4.8 的 WinForm 下(即不是 WPF 的绘图模式、也不是 Core 或 Mono 的开发框架)&#xff0c;使用 DataGridView 行模式&#xff0c;还是有个列头表现为高亮显示&#xff1a; 查找各种解决方式: 设置 ColumnHeadersDefaultCellSty…

YOLOv1代码复现2:数据加载器构建

YOLOv1代码复现2&#xff1a;数据加载器构建 前言 ​ 在经历了Faster-RCNN代码解读的摧残后&#xff0c;下决心要搞点简单的&#xff0c;于是便有了本系列的博客。如果你苦于没有博客详细告诉你如何自己去实现YOLOv1&#xff0c;那么可以看看本系列的博客&#xff0c;也许可以帮…

【Java实战篇】Day13.在线教育网课平台--生成支付二维码与完成支付

文章目录 一、需求&#xff1a;生成支付二维码1、需求分析2、表设计3、接口定义4、接口实现5、完善controller 二、需求&#xff1a;查询支付结果1、需求分析2、表设计与模型类3、接口定义4、接口实现步骤一&#xff1a;查询支付结果步骤二&#xff1a;保存支付结果&#xff08…

VUE3如何定义less全局变量

默认已经安装好了less&#xff0c;这里不过多讲。 &#xff08;1&#xff09;首先我们需要下载一个插件依赖&#xff1a; npm i style-resources-loader --save-dev &#xff08;2&#xff09;VUE3里配置vue.config.js文件内容 代码&#xff1a; const path require("p…

HashMap如何解决哈希冲突

HashMap如何解决哈希冲突 Hash算法和Hash表Hash冲突解决哈希冲突的方法开放地址法链式寻址法再hash法建立公共溢出区 Hash算法和Hash表 Hash算法就是把任意长度的输入通过散列算法编程固定长度的输出。这个输出结果就是一个散列值。 Hash表又称为“散列表”&#xff0c;它是通…

SpringBoot中一个注解优雅实现重试Retry框架

目录: 1、简介2、实现步骤 1、简介 重试&#xff0c;在项目需求中是非常常见的&#xff0c;例如遇到网络波动等&#xff0c;要求某个接口或者是方法可以最多/最少调用几次&#xff1b;实现重试机制&#xff0c;非得用Retry这个重试框架吗&#xff1f;那肯定不是&#xff0c;相信…

Mysql 查询同类数据中某一数字最大的所有数据

方法一、将时间进行排序后再分组 该表表名为customer, park_id表示园区id&#xff0c;joined_at表示用户的加入时间&#xff0c;created_at表示用户的创建时间。 需求&#xff1a;查出每个园区中&#xff0c;最早加入园区的第一位用户 select * from (select * from custome…

数据库课设--基于Python+MySQL的餐厅点餐系统(表的设计)

文章目录 一、系统需求分析二、系统设计1. 功能结构设计2、概念设计2.2.1 bill_food表E-R图2.2.2 bills表E-R图2.2.3 categories E-R图2.2.4 discounts表 E-R图2.2.5 emp表E-R图2.2.6 food 表E-R图2.2.7 member表E-R图2.2.8 member_point_bill表E-R图2.2.9 servers表E-R图2.2.1…

操作系统考试复习—第二章 2.1 2.2程序和进程的描述

第二章 进程的描述与控制 程序&#xff1a;有序的指令集合 程序顺序执行的特征&#xff1a;1.顺序性 2.封闭性 3.可再现性(确定性) 在多道程序环境下&#xff0c;允许多个程序并发执行&#xff0c;此时他们将失去封闭性&#xff0c;并具有间断性和不可再现性的特征。为此引…

基于SGM431的电路设计问题分析

本案例中,采用SGM431芯片设计了一个过压保护电路。 这个电路初次设计,有很多的问题,下面逐一分析 1.当输入24V,测得Vref=1.59V。Vout为1.15V;,mos管关断 2。经过多次测量发现,临界值在10V到10.5之间; 当输入10.5V时,测量Vref=1.69V。vout=1.15V;mos管关断 当输入1…

智慧物联网边缘协同感知(EICS)技术方案: 低功耗无线扫描唤醒技术

物联网的传感器或控制节点通常有体积限制&#xff0c;只能使用钮扣电池、小型电池&#xff0c;甚至使用能量收集源进行运作。在许多工业应用中&#xff0c;需要人工更换电池的成本&#xff0c;特别是在难以接近地方更换所需的成本&#xff0c;使得人们更加重视降低平均电流消耗…

深度学习入门到实践:相关基础概述

绪论 深度学习&#xff08;Deep Learning&#xff09;是近年来发展十分迅速的研究领域&#xff0c;并且在人工智能的很多子领域都取得了巨大的成功。从根源来讲&#xff0c;深度学习是机器学习的一个分支&#xff0c;是指一类问题以及解决这类问题的方法。     深度学习问题…

halcon灰度积分投影/垂直积分投影

简介:关于灰度投影积分可以用到的场合很多,例如分割字符,分割尺子上的刻度等,适用于有规律的变化这些内容的检测。本文复现了论文《基于深度学习和灰度纹理特征的铁路接触网绝缘子状态检测》中灰度积分投影实现了对绝缘子缺陷位置的检测。见(图1)灰度积分垂直方向投影获得…

AI智能智能课程第四讲 -数据库领域专家

使用chatGPT让你成为数据库领域专家 作业 现在要测试电商的下单功能&#xff1a;测试员张三在公司的电商平台上下了几个单&#xff0c;现在需要验证&#xff1a;张三这个客户下单的所有订单信息&#xff0c;包含订单编号&#xff0c;商品名称&#xff0c;商品价格&#xff0c;…

分支和循环语句(2)

文章目录 3.2 for循环3.2.1 for语句的语法3.2.2 for循环中的break和continue3.2.3 for语句的循环控制变量3.2.4 一些for循环的变种3.2.5 一道笔试题 3.3 do while循环3.3.1 do语句的语法3.3.2 do语句的特点3.3.3 do while循环中的break和continue 3.4 练习3.4.1 计算 n的阶乘3.…