SpringBoot系列——使用Spring Cache和Redis实现查询数据缓存

文章目录

    • 1. 前言
    • 2. 缓存
      • 2.1 什么是缓存
      • 2.2 使用缓存的好处
      • 2.3 缓存的成本
      • 2.4 使用Spring Cache和Redis的优点
    • 3. Spring Cache基础知识
      • 3.1 Spring Cache的核心概念
      • 3.2 Spring Cache的注解
        • 3.2.1 SpEL表达式
        • 3.2.2 @Cacheable
        • 3.2.3 @CachePut
        • 3.2.4 @CacheEvict
    • 4. 实现查询数据缓存
      • 4.1 准备工作
      • 4.2 添加依赖
      • 4.3 修改配置文件
      • 4.4 配置缓存管理器
      • 4.5 使用Spring Cache注解
      • 4.6 测试
        • 4.6.1 查询测试
        • 4.6.2 更新、删除测试
    • 5. 总结

1. 前言

在现代应用程序中,查询缓存的使用已经变得越来越普遍。它不仅能够显著提高系统的性能,还能提升用户体验。缓存通过在内存中存储频繁访问的数据,减少对数据库或其他存储系统的访问,从而加快数据读取速度。在这篇文章中,我们将探讨缓存的基本概念、重要性以及如何使用Spring Cache和Redis实现查询数据缓存 。

2. 缓存

2.1 什么是缓存

缓存是一种临时存储机制,用于在内存中保存频繁访问的数据。它可以是硬件(如CPU缓存)或软件(如应用程序缓存)。缓存的主要目的是通过减少数据访问的延迟,提高系统的响应速度。以下是缓存的一些关键特性:

  • 临时性:缓存中的数据通常是临时的,会在一段时间后失效或被替换。
  • 快速访问:由于缓存数据存储在内存中,访问速度非常快。
  • 空间有限:缓存的存储空间通常有限,因此需要有效的管理策略,如LRU(最近最少使用)策略。

2.2 使用缓存的好处

  1. 提高性能:缓存可以显著减少数据读取的时间,因为内存访问速度比硬盘或网络存储快很多。
  2. 减轻数据库负载:缓存可以减少数据库的查询次数,从而减轻数据库的负载,提升整体系统的稳定性和可扩展性。
  3. 节省资源:通过减少对后端系统的访问,缓存可以帮助节省带宽和计算资源。
  4. 提高用户体验:快速的数据访问可以显著提升用户体验,特别是在需要频繁读取数据的应用场景中。

2.3 缓存的成本

  1. 内存消耗:缓存需要占用系统的内存资源,过多的缓存可能会影响其他应用程序的性能。
  2. 数据一致性:缓存中的数据可能会与数据库中的数据不一致,尤其是在数据频繁更新的场景中。需要设计有效的缓存失效策略来保证数据的一致性。
  3. 复杂性增加:引入缓存机制会增加系统的复杂性,需要处理缓存的管理、更新和失效等问题。
  4. 维护成本:缓存系统需要定期监控和维护,以确保其高效运行。

2.4 使用Spring Cache和Redis的优点

为了实现高效的数据缓存,Spring Boot提供了Spring Cache模块,而Redis则是一个强大的缓存数据库。结合使用Spring Cache和Redis,能够充分发挥二者的优点,实现高效的数据缓存。

  • Spring Cache的优点
    • 简化缓存操作:Spring Cache提供了一系列注解(如@Cacheable@CachePut@CacheEvict),简化了缓存的使用,使开发者能够专注于业务逻辑。
    • 灵活的缓存管理:Spring Cache支持多种缓存提供者(如EhCache、Hazelcast、Redis等),可以根据具体需求选择合适的缓存实现。
    • 透明的缓存机制:Spring Cache使得缓存操作对业务代码透明,开发者无需关心缓存的具体实现细节。
  • Redis的优点
    • 高性能:由于数据存储在内存中,Redis的读写速度非常快,能够处理每秒数百万级别的请求。
    • 丰富的数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,能够满足不同场景下的数据存储需求。
    • 持久化支持:虽然Redis主要用于内存存储,但它也提供了数据持久化的功能,可以将数据定期保存到磁盘,防止数据丢失。
    • 分布式支持:Redis支持主从复制、哨兵模式和集群模式,能够实现高可用性和数据的水平扩展。
    • 灵活的过期策略:Redis支持为每个键设置过期时间,自动删除过期数据,方便实现缓存失效策略。

3. Spring Cache基础知识

在Spring Boot中,Spring Cache提供了一套简洁且强大的缓存抽象机制,帮助开发者轻松地将缓存集成到应用程序中。以下是Spring Cache的一些核心概念和常用注解。

3.1 Spring Cache的核心概念

  1. CacheManager

    • 定义CacheManager是Spring Cache的核心接口,负责管理多个缓存实例。它是缓存操作的入口点,提供了获取和操作缓存实例的方法。
    • 实现:Spring提供了多种CacheManager实现,如ConcurrentMapCacheManagerEhCacheCacheManagerRedisCacheManager等。不同的实现适用于不同的缓存存储机制。
  2. Cache

    • 定义Cache是缓存的具体实现,负责存储和检索缓存数据。它提供了基本的缓存操作,如putgetevict等。
    • 实现:具体的Cache实现依赖于底层的缓存存储机制,如内存缓存、Redis缓存等。

3.2 Spring Cache的注解

3.2.1 SpEL表达式

因为Spring Cache使用SpEL表达式来动态生成缓存键,所以在学习Spring Cache的注解之前我们还要先简单了解一下SpEL表达式的语法,这部分可以先不看懂,在后面看注解的时候回来看即可。

SpEL表达式的语法类似于Java的表达式语法,支持以下几种操作:

  1. 字面量
    • 数字:1, 2.5
    • 字符串:'hello', "world"
    • 布尔值:true, false
    • 空值:null
  2. 属性和方法
    • 访问对象的属性:#user.name
    • 调用对象的方法:#user.getName()
  3. 运算符
    • 算术运算:+, -, *, /, %
    • 比较运算:==, !=, <, >, <=, >=
    • 逻辑运算:&&, ||, !
  4. 集合和数组
    • 访问集合元素:#users[0]
    • 集合操作:#users.size(), #users.isEmpty()
  5. 条件运算符
    • 三元运算符:condition ? trueValue : falseValue
    • Elvis运算符:expression ?: defaultValue
  6. 变量
    • 定义和使用变量:#variableName

接下来进入Spring Cache注解的学习:

3.2.2 @Cacheable
  • 作用@Cacheable注解用于标注需要缓存的方法。当该方法被调用时,Spring Cache会先检查缓存中是否存在对应的数据。如果存在,则直接返回缓存数据;如果不存在,则执行方法并将结果存入缓存。
  • 示例
    @RestController("/users")
    @RequiredArgsConstructor
    public class UserController {
    
        private final UserService userService;
        
        @Cacheable(value = "user", key = "#id")
        public User getUser(Long id) {
            // 获取用户的逻辑
            return userService.findById(id);
        }
    }
    
  • 参数
    • value:指定缓存的名称。
    • key:指定缓存的键,可以使用SpEL表达式。
3.2.3 @CachePut
  • 作用@CachePut注解用于标注需要更新缓存的方法。即使缓存中已经存在数据,该方法仍然会执行,并将结果更新到缓存中。
  • 示例
    @RestController("/users")
    @RequiredArgsConstructor
    public class UserController {
    
        private final UserService userService;
        
        @CachePut(value = "user", key = "#user.id")
        public User updateUser(User user) {
            // 更新用户的逻辑
            return userService.save(user);
        }
    }
    
  • 参数
    • value:指定缓存的名称。
    • key:指定缓存的键,可以使用SpEL表达式。
3.2.4 @CacheEvict
  • 作用@CacheEvict注解用于标注需要清除缓存的方法。当该方法被调用时,Spring Cache会清除对应的缓存数据。
  • 示例
    @RestController("/users")
    @RequiredArgsConstructor
    public class UserController {
    
        private final UserService userService;
        
        @CacheEvict(value = "user", key = "#id")
        public void deleteUser(Long id) {
            // 删除用户的逻辑
            userService.deleteById(id);
        }
    }
    
  • 参数
    • value:指定缓存的名称。
    • key:指定缓存的键,可以使用SpEL表达式。
    • allEntries:如果设置为true,则清除缓存中的所有数据。

4. 实现查询数据缓存

4.1 准备工作

  1. Redis安装与配置:

这里可以自行查找文章进行安装和配置,网上优质文章很多👻。

  1. 创建Product实体类:
@Data
@AllArgsConstructor
public class Product implements Serializable {

    private Long id;

    private String name;

    private Integer category;

    private String description;

    private Integer stock;

}
  1. 创建枚举类ResultEnum
@Getter
public enum ResultEnum {

    /* 成功状态码 */
    SUCCESS(1, "操作成功!"),

    /* 错误状态码 */
    FAIL(0, "操作失败!"),

    /* 参数错误:10001-19999 */
    PARAM_IS_INVALID(10001, "参数无效"),
    PARAM_IS_BLANK(10002, "参数为空"),
    PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"),
    PARAM_NOT_COMPLETE(10004, "参数缺失"),

    /* 用户错误:20001-29999*/
    USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),
    USER_LOGIN_ERROR(20002, "账号不存在或密码错误"),
    USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),
    USER_NOT_EXIST(20004, "用户不存在"),
    USER_HAS_EXISTED(20005, "用户已存在"),

    /* 系统错误:40001-49999 */
    FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"),
    FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"),

    /* 数据错误:50001-599999 */
    RESULT_DATA_NONE(50001, "数据未找到"),
    DATA_IS_WRONG(50002, "数据有误"),
    DATA_ALREADY_EXISTED(50003, "数据已存在"),
    AUTH_CODE_ERROR(50004, "验证码错误"),


    /* 权限错误:70001-79999 */
    PERMISSION_UNAUTHENTICATED(70001, "此操作需要登陆系统!"),

    PERMISSION_UNAUTHORIZED(70002, "权限不足,无权操作!"),

    PERMISSION_EXPIRE(70003, "登录状态过期!"),

    PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),

    PERMISSION_LIMIT(70005, "访问次数受限制"),

    PERMISSION_TOKEN_INVALID(70006, "无效token"),

    PERMISSION_SIGNATURE_ERROR(70007, "签名失败");

    // 状态码
    int code;
    // 提示信息
    String message;

    ResultEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int code() {
        return code;
    }

    public String message() {
        return message;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  1. 创建统一返回结果封装类Result

相关文章可以看这里:Spring Boot3统一结果封装

@Data
@NoArgsConstructor
public class Result<T> implements Serializable {

    // 操作代码
    Integer code;

    // 提示信息
    String message;

    // 结果数据
    T data;

    public Result(ResultEnum resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(ResultEnum resultCode, T data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }
    public Result(String message) {
        this.message = message;
    }
    //成功返回封装-无数据
    public static Result<String> success() {
        return new Result<String>(ResultEnum.SUCCESS);
    }
    //成功返回封装-带数据
    public static <T> Result<T> success(T data) {
        return new Result<T>(ResultEnum.SUCCESS, data);
    }
    //失败返回封装-使用默认提示信息
    public static Result<String> error() {
        return new Result<String>(ResultEnum.FAIL);
    }
    //失败返回封装-使用返回结果枚举提示信息
    public static Result<String> error(ResultEnum resultCode) {
        return new Result<String>(resultCode);
    }
    //失败返回封装-使用自定义提示信息
    public static Result<String> error(String message) {
        return new Result<String>(message);

    }
}

4.2 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.3 修改配置文件

spring:
  data:
    redis:
      # Redis服务器地址
      host: ${shijun.redis.host}
      # Redis服务器端口
      port: ${shijun.redis.port}
      # Redis服务器认证密码
      password: ${shijun.redis.password}
      # Redis数据库索引
      database: ${shijun.redis.database}

4.4 配置缓存管理器

/**
 * 配置类,用于设置缓存管理器及相关配置,以启用缓存功能
 *
 * @author shijun
 * @date 2024/06/13
 */
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    /**
     * 配置Redis键的序列化方式
     *
     * @return StringRedisSerializer,用于序列化和反序列化Redis中的键
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * 配置Redis值的序列化方式
     *
     * @return GenericJackson2JsonRedisSerializer,使用Jackson库以JSON格式序列化和反序列化Redis中的值
     */
    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    /**
     * 缓存前缀,用于区分不同的缓存命名空间,一般以模块名或者服务名命名,这里暂时写cache
     */
    public static final String CACHE_PREFIX = "cache:";

    /**
     * 配置缓存管理器,使用Redis作为缓存后端
     *
     * @param redisConnectionFactory Redis连接工厂,用于创建Redis连接
     * @return RedisCacheManager,Redis缓存管理器实例
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 配置序列化,解决乱码的问题,设置缓存名称的前缀和缓存条目的默认过期时间
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 设置键的序列化器
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                // 设置值的序列化器
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                // 设置缓存名称的前缀
                .computePrefixWith(name -> CACHE_PREFIX + name + ":")
                // 设置缓存条目的默认过期时间为300秒
                .entryTtl(Duration.ofSeconds(300));

        // 创建非锁定的Redis缓存写入器
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisConnectionFactory));

        // 返回Redis缓存管理器实例,使用上述配置
        return new RedisCacheManager(redisCacheWriter, config);
    }

}

分析:
StringRedisSerializer :使用 StringRedisSerializer 将缓存的键序列化为字符串。因为Redis中的键通常是字符串类型,使用字符串序列化器可以确保键在Redis中以可读的形式存储,便于调试和管理。

GenericJackson2JsonRedisSerializer :使用 GenericJackson2JsonRedisSerializer 将缓存的值序列化为JSON格式,可读性高并且便于人工排查数据。

4.5 使用Spring Cache注解

由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有数据一致性问题存在,在一些并发场景会出现问题。

这里采用Cache Aside Pattern 即旁路缓存模式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案。

  • 读流程:
    image-20240613224855076

分析:

  1. 应用程序首先从缓存中查找数据。
  2. 如果缓存命中,则直接返回缓存中的数据。
  3. 如果缓存未命中,则从数据库中读取数据,并将读取到的数据写入缓存,以便
  4. 后续请求可以直接从缓存中获取。
  • 写流程
    image-20240613221100300

分析:

  1. 应用程序首先更新数据库中的数据。
  2. 然后使缓存中的对应数据失效
@Slf4j
@RestController("/products")
public class ProductController {


    /**
     * 根据ID获取产品信息
     * 通过@Cacheable注解,当请求的产品ID在缓存中存在时,直接从缓存中获取产品信息,减少数据库查询
     *
     * @param id 产品ID
     * @return 返回查询结果,包含指定ID的产品信息
     */
    @GetMapping("/getProductById")
    @Cacheable(value = "productsCache", key = "#id")
    public Result<Product> getProductById(Long id) {
        // 当从数据库获取数据时会打印,如果是从缓存中查询并不会执行到这里。
        log.info("从数据库获取产品: id = {}", id);
        Product product = new Product(id, "product", 100, "课本", 10);
        return Result.success(product);
    }

    /**
     * 更新产品信息
     * 通过@CacheEvict注解,当更新产品时,清除缓存中对应产品的数据,确保获取到最新的数据
     * 设置allEntries为true,表示清除整个缓存中的所有产品数据
     *
     * @param product 产品对象,包含更新后的详细信息
     * @return 返回更新结果,成功更新时返回成功标志
     */
    @PutMapping("/updateProduct")
    @CacheEvict(value = "productsCache", key = "#product.id")
    public Result updateProduct(@RequestBody Product product) {
        // 更新操作
        return Result.success();
    }

    /**
     * 删除指定ID的产品
     * 通过@CacheEvict注解,当删除产品时,清除缓存中对应产品的数据
     *
     * @param id 待删除产品的ID
     * @return 返回删除结果,成功删除时返回成功标志
     */
    @DeleteMapping("/deleteProductById")
    @CacheEvict(value = "productsCache", key = "#id")
    public Result deleteProductById(Long id) {
        // 删除操作
        return Result.success();
    }


}

4.6 测试

4.6.1 查询测试
  1. 发送查询请求:
image-20240613203320506
  1. 查看Redis中的缓存数据:

通过观察可以发现CacheConfig类中的序列化配置起作用了,Redis中的数据不再是一堆乱码,并且在右上角还有我们之前配置的缓存的过期时间(我们之前配置的300s)。

image-20240613203847598
  1. 查看控制台发现本次查询为从数据库查询:
image-20240613212356495
  1. 再次发送会发现数据成功的查询了:
image-20240613203438434
  1. 再次查询控制台发现并没有输出从数据库获取产品: id = 1,说明本次查询为从Redis缓存中获取数据。
4.6.2 更新、删除测试

分别发送更新请求和删除请求,然后再次查看Redis中的缓存数据:

image-20240613215742944 image-20240613221930757 可以发现Redis当中对应的缓存数据被删除了,符合我们的设计: image-20240613204329734

5. 总结

在本文中,我们详细介绍了如何在Spring Boot项目中使用Spring Cache和Redis实现数据缓存,并简单讲解了使用Cache Aside Pattern来解决数据一致性问题,希望对大家学习有所帮助。如有问题,大家可以私信或者在评论区询问😊。

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

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

相关文章

量化交易入门——盘口

今天接着上一期讲解开盘定势的种类&#xff0c;在讲之前&#xff0c;科普一下“盘口五档”的成交知识。 每个炒股软件上&#xff0c;都会有某只个股的成交信息&#xff0c;在其中会出现一个五档的行情列表&#xff0c;里面列出了买家和卖家各五个价格及其对应的数量。这五档价…

深入浅出 Go 语言的 GPM 模型(Go1.21)

引言 在现代软件开发中&#xff0c;有效地利用并发是提高应用性能和响应速度的关键。随着多核处理器的普及&#xff0c;编程语言和框架如何高效、简便地支持并发编程&#xff0c;成为了软件工程师们评估和选择工具时的一个重要考量。在这方面&#xff0c;Go 语言凭借其创新的并…

基于51单片机的教室智能照明控制系统

一.硬件方案 本系统以51单片机作为控制模块的核心部件&#xff0c;采用热释红外人体传感器检测人体的存在&#xff0c;采用光敏三极管构成的电路检测环境光的强度&#xff1b;根据教室合理开灯的条件&#xff0c;通过对人体存在信号和环境光信号的识别与判断&#xff0c;完成对…

MySQL的增删查改(CRUD)

目录 一.CRUD 1.什么是CRUD 2.CRUD的特点 二.新增&#xff08;Create&#xff09; 单列插入全行数据 表的复制 额外小知识 三.阅读(Read) 1.全表查询指定列查询 2.查询字段为表达式 3.别名 ​编辑 4.去重 5.排序 1.根据列名进行排序 2.使用表达式及别名进行排序…

PyTorch -- 最常见激活函数的选择

首先&#xff0c;简单复习下什么是梯度&#xff1a;梯度是偏微分的集合 举例说明&#xff1a;对于 z y 2 − x 2 : ∇ z ( ∂ z ∂ x , ∂ z ∂ y ) &#xff08; 2 x , 2 y &#xff09; z y^2-x^2: \nabla z (\frac{\partial z}{\partial x}, \frac{\partial z}{\partia…

文件操作(总)

1.为什么使用文件 a是内存上的一块区域 如果程序退出&#xff0c;则内存回收&#xff0c;数据丢失了&#xff0c;再次运行就看不到上次的数据了 为了持久化保存&#xff0c;我们可以使用文件 2.文件是什么 磁盘上的文件是文件 在程序设计中&#xff0c;文件有两种&#xff…

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

目录 一、ThreadLocal基本知识回顾分析 &#xff08;一&#xff09;ThreadLocal原理 &#xff08;二&#xff09;既然ThreadLocalMap的key是弱引用&#xff0c;GC之后key是否为null&#xff1f; &#xff08;三&#xff09;ThreadLocal中的内存泄漏问题及JDK处理方法 &…

【StableDiffusion】Embedding 底层原理,Prompt Embedding,嵌入向量

Embedding 是什么&#xff1f; Embedding 是将自然语言词汇&#xff0c;映射为 固定长度 的词向量 的技术 说到这里&#xff0c;需要介绍一下 One-Hot 编码 是什么。 One-Hot 编码 使用了众多 5000 长度的1维矩阵&#xff0c;每个矩阵代表一个词语。 这有坏处&#xff0c…

vscode卡顿问题处理(vue-official插件)

vue官方扩展由volar升级为vue-official&#xff0c;部分人的ide会变得非常卡顿&#xff0c;这是由于vscode本身一些问题导致&#xff0c;如下图作者解释&#xff1a; 解决方式&#xff1a; 通过禁用Hybrid模式&#xff0c;不使用tsserver来接管语言支持&#xff0c;卡顿会缓解…

进击算法工程师深度学习课程

"进击算法工程师深度学习课程"旨在培养学员在深度学习领域的专业技能和实战经验。课程涵盖深度学习基础理论、神经网络架构、模型优化方法等内容&#xff0c;通过项目实践和算法实现&#xff0c;帮助学员掌握深度学习算法原理和应用&#xff0c;提升在算法工程师领域…

如何用多媒体沙盘实现智能交互体验?

随着多媒体技术在内容展示领域的迅猛进步&#xff0c;智能化信息交互方式已然跃升为公众瞩目的焦点&#xff0c;而展厅作为信息传递与产品展示的核心阵地&#xff0c;正面临着提升交互体验、强化信息传递效果的迫切需求。因此&#xff0c;以多媒体沙盘、LED屏幕等创新装置为媒介…

94. 二叉树的中序遍历(Swift实现, 迭代)

题目描述 使用迭代方法解题 class TreeNode {var val: Intvar left: TreeNode?var right: TreeNode?init(_ val: Int) {self.val valself.left nilself.right nil} }func inorderTraversal(_ root: TreeNode?) -> [Int] {var result [Int]() // 用于存储中序遍历…

[深度学习]基于C++和onnxruntime部署yolov10的onnx模型

基于C和ONNX Runtime部署YOLOv10的ONNX模型&#xff0c;可以遵循以下步骤&#xff1a; 准备环境&#xff1a;首先&#xff0c;确保已经下载后指定版本opencv和onnruntime的C库。 模型转换&#xff1a;按照官方源码&#xff1a;https://github.com/THU-MIG/yolov10 安装好yolov…

【OpenVINO™】使用 OpenVINO™ C++ 异步推理接口部署YOLOv8 ——在Intel IGPU 上实现80+FPS视频推理

​ OpenVINO Runtime支持同步或异步模式下的推理。Async API的主要优点是&#xff0c;当设备忙于推理时&#xff0c;应用程序可以并行执行其他任务&#xff08;例如&#xff0c;填充输入或调度其他请求&#xff09;&#xff0c;而不是等待当前推理首先完成。 当我们使用异步API…

图片查看器

目录 一 原型 二 源码 一 原型 二 源码 namespace 图片查看器 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){//默认显示第一张图片pictureBox1.Image imageList1.Images[0];}private v…

13. 第十三章 案例研究-选择数据结构

13. 案例研究-选择数据结构 到这里尼应该已经学会了Python的核心数据结构, 也见过了一些使用它们的算法. 如果你想要更多地了解算个发可以阅读第21章. 本章配合联系介绍一个案例分析, 帮你思考如何选择数据结构并如何使用它们.13.1 单词频率分析 1. 练习1 编写一个程序, 读入…

《Brave New Words 》9.1 AI 世界中的就业

Part IX: Work and What Comes Next 第九部分&#xff1a;工作及其未来发展 The one who plants trees, knowing that he will never sit in their shade, has at least started to understand the meaning of life. —Rabindranath Tagore 种树的人&#xff0c;虽然知道他永远…

上位机图像处理和嵌入式模块部署(h750 mcu串口命令处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面学习103和407的时候&#xff0c;当时学过串口的收发。不过当时使用的主要是阻塞的方式。这一次&#xff0c;我们看下应该怎么利用中断的形式进…

基于flask的网站如何使用https加密通信

文章目录 内容简介网站目录示例生成SSL证书单独使用Flask使用WSGI服务器Nginx反向代理参考资料 内容简介 HTTPS 是一种至关重要的网络安全协议&#xff0c;它通过在 HTTP 协议之上添加 SSL/TLS 层来确保数据传输的安全性和完整性。这有助于防止数据在客户端和服务器之间传输时…

前端实现获取后端返回的文件流并下载

前端实现获取后端返回的文件流并下载 方法一&#xff1a;使用Axios实现文件流下载优点缺点 方法二&#xff1a;使用封装的Request工具实现文件流下载优点缺点 方法三&#xff1a;直接通过URL跳转下载优点缺点 结论 在前端开发中&#xff0c;有时需要从后端获取文件流&#xff0…