当前位置: 首页 > news >正文

SpringBoot 使用 Cache 集成 Redis做缓存保姆教程

1. 项目背景

Spring Cache是Spring框架提供的一个缓存抽象层,它简化了缓存的使用和管理。Spring Cache默认使用服务器内存,并无法控制缓存时长,查找缓存中的数据比较麻烦。

因此Spring Cache支持将缓存数据集成到各种缓存中间件中。本文已常用的Redis作为缓存中间件作为示例,详细讲解项目中如何使用Cache提高系统性能。

2. Spring Cache介绍

Spring Cache是Spring框架提供的一种缓存解决方案,基于AOP原理,实现了基于注解的缓存功能,只需要简单地加一个注解就能实现缓存功能,对业务代码的侵入性很小。

使用Spring Cache的方法很简单,只需要在方法上添加注解即可实现将方法返回数据存入缓存,以及清理缓存等注解的使用。

2.1 主要特点

  1. 统一的缓存抽象:Spring Cache为应用提供了一种统一的缓存抽象,可以轻松集成各种缓存提供者(如Ehcache、Redis、Caffeine等),使用统一的API。
  2. 注解驱动:Spring Cache通过简单的注解配置,如@Cacheable@CachePut@CacheEvict等,可以快速实现缓存功能,而无需处理底层缓存逻辑。
  3. 灵活性和扩展性:Spring Cache允许根据业务需求自定义缓存策略,如缓存的失效时间、缓存的淘汰策略等。同时,它也提供了CacheManager接口和Cache接口,可以实现降低对各种缓存框架的耦合。

2.2 常用注解

@EnableCaching
  • 作用:开启Spring的缓存注解支持。
  • 使用场景:在配置类上添加此注解,以启用Spring Cache的注解处理功能。
  • 注意:此注解本身并不提供缓存实现,而是允许你使用@Cacheable@CachePut@CacheEvict等注解来定义缓存行为。
@Cacheable
  • 作用:在方法执行前检查缓存,如果缓存中存在数据则直接返回,否则执行方法并将结果缓存。
  • value:指定缓存的名称(或名称数组)。缓存名称与CacheManager中配置的缓存对应。
  • key:用于生成缓存键的表达式(可选)。如果不指定,则默认使用方法的参数值作为键。
  • condition:条件表达式(可选),用于决定是否执行缓存操作。
  • unless:否定条件表达式(可选),用于在方法执行后决定是否缓存返回值。
@Cacheable注解配置参数说明
  1. value/cacheNames

    • 用于指定缓存的名称(或名称数组),缓存名称作为缓存key的前缀。这是缓存的标识符,用于在CacheManager中查找对应的缓存。
    • valuecacheNames是互斥的,即只能指定其中一个。
  2. key

    • 用于生成缓存键的表达式。这个键用于在缓存中唯一标识存储的值。
    • 如果不指定key,则默认使用方法的参数值(经过某种转换)作为键。
    • 可以使用Spring Expression Language(SpEL)来编写key表达式,以实现动态键的生成。
  3. keyGenerator

    • 指定一个自定义的键生成器(实现 org.springframework.cache.interceptor.KeyGenerator 接口的类),用于生成缓存的键。与 key 属性互斥,二者只能选其一。
    • 如果同时指定了keykeyGenerator,则会引发异常,因为它们是互斥的。
    • 开发者可以编写自己的KeyGenerator实现,并将其注册到Spring容器中,然后在@Cacheable注解中引用。
  4. cacheManager

    • CacheManager表示缓存管理器,通过缓存管理器可以设置缓存过期时间。
    • 用于指定要使用的CacheManager。这是一个可选参数,通常不需要显式指定,因为Spring会默认使用配置的CacheManager。
    • 如果系统中配置了多个CacheManager,则需要通过此参数指定使用哪一个。
  5. cacheResolver

    • 缓存解析器,用于解析缓存名称并返回相应的Cache对象。这也是一个可选参数。
    • 类似于cacheManager,如果系统中配置了多个缓存解析逻辑,可以通过此参数指定使用哪一个。
  6. condition

    • 条件表达式,用于决定是否执行缓存操作。这是一个可选参数。
    • 条件表达式使用SpEL编写,如果表达式返回true,则执行缓存操作;否则不执行。
  7. unless

    • 否定条件表达式,用于在方法执行后决定是否缓存返回值。这也是一个可选参数。
    • condition类似,unless也使用SpEL编写,但它是在方法执行后才进行评估的。
    • 如果unless表达式返回true,则不缓存返回值;否则缓存。
  8. sync

    • 是否使用异步模式进行缓存操作。这是一个可选参数,通常不需要显式指定。
    • 在多线程环境中,如果多个线程同时请求相同的数据并触发缓存操作,使用异步模式可以避免线程阻塞和重复计算。

@Cacheable注解的这些参数是互斥或相互关联的,例如valuecacheNames不能同时指定,keykeyGenerator也不能同时指定。此外,cacheManagercacheResolver也是互斥的,因为它们都用于指定缓存的解析和管理方式。

对于前两个注解的应用:

    @Cacheable(cacheNames = "cache:cacheByKey", key = "#id")public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByKey方法" + id);return id;}

看注释掉的那行,取缓存名称为cache:cacheByKey,参数id的值作为key,最终缓存key为:缓存名称+“::”+key,例如:上述代码id为123,最终的key为:cache:cacheByKey::123

SpEL(Spring Expression Language)是一种在 Spring 框架中用于处理字符串表达式的强大工具,它可以实现获取对象的属性,调用对象的方法操作。

  • 单个缓存名称@Cacheable(value = "myCache") 表示使用名为myCache的缓存。
  • 多个缓存名称@Cacheable(value = {"cache1", "cache2"}) 表示方法的结果将同时缓存到cache1cache2中。
  • @CacheConfig结合使用:如果类上使用了@CacheConfig注解,并且指定了cacheNames属性,那么类中的方法在使用@Cacheable时可以省略value属性,直接使用类级别的缓存配置。
@CacheEvict
  • 作用:从缓存中删除数据。
  • value:指定要删除的缓存的名称(或名称数组)。
  • key:用于指定要删除的缓存键(可选)。如果不指定,则默认使用方法的参数值作为键。
  • allEntries:布尔值,指定是否删除缓存中的所有条目(而不是仅删除与指定键匹配的条目)。
  • beforeInvocation:布尔值,指定是否在方法执行之前删除缓存(默认为false,即在方法执行之后删除)。
@CachePut
  • 作用:更新缓存中的数据,无论方法是否成功执行,都会将结果放入缓存。
  • valuekeyconditionunless:与@Cacheable中的这些属性相同。
@Caching
  • 作用:允许在同一个方法上组合使用多个缓存注解(如@Cacheable@CachePut@CacheEvict)。
  • 属性:包含一个或多个缓存注解。
@CacheConfig
  • 作用:为类级别提供缓存相关的默认配置。
  • cacheNames:指定该类中所有方法使用的默认缓存名称(或名称数组)。
  • keyGenerator:指定自定义的键生成器(可选)。
  • cacheManager:指定要使用的CacheManager(可选)。

3. 示例代码

项目依赖于Redis配置,这里就不多赘述了。

缓存管理器配置:

定义了两个缓存管理器,默认cacheManager(使用@Primary标注),一个缓存返回值为null的管理器cacheNullManager,详情看下面代码。

package com.maple.redis.config;import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.lang.reflect.Method;
import java.time.Duration;/*** @author 笑小枫* @date 2025/1/7*/
@Slf4j
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {/*** 默认缓存管理器* 只有CacheManger才能扫描到cacheable注解* spring提供了缓存支持Cache接口,实现了很多个缓存类,其中包括RedisCache。但是我们需要对其进行配置,这里就是配置RedisCache*/@Bean@Primarypublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {return RedisCacheManager.RedisCacheManagerBuilder//Redis链接工厂.fromConnectionFactory(redisConnectionFactory)//缓存配置 通用配置  默认存储一小时.cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))//配置同步修改或删除  put/evict.transactionAware()//对于不同的cacheName我们可以设置不同的过期时间.withCacheConfiguration("cache2:cacheByUser", getCacheConfigurationWithTtl(Duration.ofHours(2))).build();}/*** 创建并返回一个CacheManager Bean,用于管理Redis缓存。* 主要返回结果为null时使用,会缓存null值,缓存时间为10分钟,防止缓存穿透。* 使用时通过 cacheManager = "cacheNullManager" 指定使用该缓存管理器。*/@Beanpublic CacheManager cacheNullManager(RedisConnectionFactory redisConnectionFactory) {return RedisCacheManager.RedisCacheManagerBuilder//Redis链接工厂.fromConnectionFactory(redisConnectionFactory)//缓存配置 通用配置  默认存储一小时.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()// 设置key为String.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))// 设置value 为自动转Json的Object.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())).entryTtl(Duration.ofMinutes(10)))//配置同步修改或删除  put/evict.transactionAware().build();}/*** 缓存的基本配置对象*/private RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {return RedisCacheConfiguration.defaultCacheConfig()//设置key value的序列化方式// 设置key为String.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))// 设置value 为自动转Json的Object.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))// 不缓存null.disableCachingNullValues()// 设置缓存的过期时间.entryTtl(duration);}/*** 缓存的异常处理*/@Bean@Overridepublic CacheErrorHandler errorHandler() {// 异常处理,当Redis发生异常时,打印日志,但是程序正常走log.info("初始化 -> [{}]", "Redis CacheErrorHandler");return new CacheErrorHandler() {@Overridepublic void handleCacheGetError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);}@Overridepublic void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);}@Overridepublic void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);}@Overridepublic void handleCacheClearError(RuntimeException e, Cache cache) {log.error("Redis occur handleCacheClearError:", e);}};}@Override@Bean("myKeyGenerator")public KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuffer sb = new StringBuffer();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}
}

使用案例:

User对象就idname两个字段,大家随意~

package com.maple.redis.controller;import com.maple.redis.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.web.bind.annotation.*;/*** @author 笑小枫* @date 2025/1/7*/
@Slf4j
@RestController
@RequestMapping("/cache")
public class TestCacheController {/*** 获取简单缓存数据。** <p>通过@Cacheable注解,该方法的结果会被缓存到名为"cache:simpleCache"的缓存中。* 如果在缓存中找到相同请求的结果,将直接返回缓存的值,避免重复执行方法体中的逻辑。** <p>方法内部,使用Thread.sleep(5000)模拟了一个耗时操作,*/@GetMapping("/simpleCache")@Cacheable(cacheNames = "cache:simpleCache")public String simpleCache() throws InterruptedException {Thread.sleep(5000);log.info("执行了simpleCache方法");return "test";}/*** 如果缓存中存在对应的ID,则直接从缓存中获取结果,避免重复执行耗时操作。* 如果缓存中不存在,则执行方法体中的逻辑,将结果存入缓存并返回。* 方法执行过程中,通过Thread.sleep模拟了一个耗时操作。*/@GetMapping("/{id}")@Cacheable(cacheNames = "cache:cacheByKey", key = "#id")public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByKey方法" + id);return id;}/*** <p>该方法使用@Caching注解集成了多个缓存策略:</p>* <ul>*     <li>*         当方法返回值为null时(即缓存穿透情况),使用名为"cacheNullManager"的CacheManager进行缓存处理,*         缓存名称为"cache2:cacheByKey",缓存键为传入的用户ID,并设置缓存过期时间为10分钟。*         这通过@Cacheable注解的cacheManager属性指定缓存管理器,unless属性设置缓存条件(当结果为null时缓存)。*     </li>*     <li>*         当方法返回值不为null时,使用默认的CacheManager进行缓存处理,*         缓存名称和键的设置与上述相同,但此时缓存管理器为默认配置。*         这通过另一个@Cacheable注解实现,其unless属性设置为当结果为null时不缓存。*     </li>* </ul>** <p>在方法执行过程中,通过Thread.sleep模拟了一个耗时操作。</p>*/@Caching(cacheable = {//result为null时,属于缓存穿透情况,使用cacheNullManager缓存管理器进行缓存,并且设置过期时间为10分钟。@Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result != null", cacheManager = "cacheNullManager"),//result不为null时,使用默认缓存管理器进行缓存。@Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result == null")})@GetMapping("/cacheMore/{id}")public User cacheMore(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(5000);if (id > 100) {return null;} else {return new User(id, "zhangsan");}}@PostMapping("/cacheByUser")@Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id")public User cacheByUser(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByUser方法" + user.getId());return user;}@PostMapping("/cacheByIdAndName")@Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id")public User cacheByIdAndName(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByUser方法" + user.getId());return user;}/*** 根据用户ID大于100的条件进行缓存处理。** @param user 用户对象,包含用户ID等信息。* @return 返回传入的用户对象。* @throws InterruptedException 如果线程被中断,则抛出此异常。**                              通过@Cacheable注解实现了缓存功能,当请求的用户ID大于100时,会触发缓存机制。*                              缓存的名称设置为"cache2:cacheByUser",缓存的键为传入的用户对象的ID。*                              如果缓存中已存在对应的用户数据,则直接从缓存中获取并返回,避免重复执行耗时操作。*                              如果缓存中不存在,则执行方法体中的逻辑,将结果存入缓存并返回。*                              在方法执行过程中,通过Thread.sleep模拟了一个耗时操作。*/@PostMapping("/cacheByUserIdGt100")@Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id", condition = "#user.id > 100")public User cacheByUserIdGt100(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByUser方法" + user.getId());return user;}/*** 更新用户信息。* <p>* 使用@CachePut注解将更新后的用户信息存入缓存中。* 缓存的名称设置为"cache2:cacheByUser",缓存的键为传入的User对象的ID。* 如果缓存中已存在对应的用户数据,则更新缓存中的值;如果不存在,则创建新的缓存条目。* 在方法执行过程中,通过Thread.sleep模拟了一个耗时操作。*/@PostMapping("/updateUser")@CachePut(cacheNames = "cache2:cacheByUser", key = "#user.id")public User updateUser(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了saveUser方法" + user.getId());return user;}/*** 删除指定ID的用户,并从缓存中移除对应的数据。* <p>* 使用@CacheEvict注解用于从缓存中移除指定ID的用户数据。* 缓存的名称设置为"cache2:cacheByUser",缓存的键为传入的用户ID。* 在执行删除操作前,方法通过Thread.sleep模拟了一个耗时操作。*/@DeleteMapping("/{id}")@CacheEvict(cacheNames = "cache2:cacheByUser", key = "#id")public void deleteUser(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(10000);log.info("执行了deleteUser方法" + id);}
}

模拟了多种缓存使用的方式

  • updateUser使用@CachePut对数据进行缓存或更新。
  • deleteUser使用@CacheEvict删除缓存。
  • cacheMore根据条件选择不同的缓存管理器进行缓存数据。

简单附几张测试截图吧

第一次查询,没有缓存截图:

image-20250108110651822

后续查询走缓存的截图

image-20250108110707747

redis缓存数据格式:

image-20250108110505496

redis缓存数据详情:

image-20250108110447105

4. SpEL在Spring Cache中的应用

4.1 SpEL概述

SpEL是Spring框架提供的一种功能强大的表达式语言,它能够在运行时查询和操作对象图。SpEL的语法简洁,支持方法调用、字符串模板、集合操作、逻辑运算等复杂功能,使得在Spring配置和代码中能够更轻松地处理复杂的逻辑和数据结构。

4.2 SpEL应用

  1. 动态生成缓存键

    • 在Spring Cache中,缓存键(Key)用于在缓存中唯一标识数据。通过使用SpEL表达式,可以根据方法参数、返回值等动态生成缓存键。
    • 例如,在@Cacheable注解中,可以使用key属性配合SpEL表达式来指定缓存键的生成规则。
  2. 条件缓存

    • Spring Cache允许通过condition属性来指定缓存的条件。当条件满足时,才会执行缓存操作(如缓存数据或移除缓存)。
  3. 除非条件

    • unless属性与condition属性类似,但它用于指定不执行缓存操作的条件。
    • 当unless条件满足时,即使方法被调用,其结果也不会被缓存。
    • unless属性同样支持SpEL表达式。

4.3 SpEL表达式在Spring Cache中的常用变量

  1. #参数名

    • 表示方法参数。可以通过参数名来引用方法参数的值。
    • 例如,#param1表示第一个参数的值。
  2. #result

    • 表示方法的返回值。在@CachePut和@CacheEvict注解中,可以使用#result来引用方法的返回值。
  3. #root

    • 表示缓存表达式根对象(CacheExpressionRootObject)。它提供了对缓存操作上下文的访问。
    • 通过#root,可以获取到缓存的详细信息,如缓存名称、方法参数等。

注意:

condition属性在Spring Cache中用于在方法执行前判断是否执行缓存操作,并且不能引用方法的返回值;而unless属性则用于在方法执行后根据返回值或其他条件来决定是否缓存数据。

5. 工作原理

Spring Cache是基于AOP原理,对添加注解@Cacheable的类生成代理对象,在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用源方法获取数据返回,并缓存起来,下边跟踪Spring Cache的切面类CacheAspectSupport.java中的private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)方法。

@Nullableprivate Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {if (contexts.isSynchronized()) {CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {return this.invokeOperation(invoker);}Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);Cache cache = (Cache)context.getCaches().iterator().next();try {return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));} catch (Cache.ValueRetrievalException var10) {Cache.ValueRetrievalException ex = var10;ReflectionUtils.rethrowRuntimeException(ex.getCause());}}this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));List<CachePutRequest> cachePutRequests = new ArrayList();if (cacheHit == null) {this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}Object cacheValue;Object returnValue;if (cacheHit != null && !this.hasCachePut(contexts)) {//如果缓存有,则从缓存取cacheValue = cacheHit.get();returnValue = this.wrapCacheValue(method, cacheValue);} else {//缓存没有,执行原始方法returnValue = this.invokeOperation(invoker);cacheValue = this.unwrapReturnValue(returnValue);//再存缓存}this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);Iterator var8 = cachePutRequests.iterator();while(var8.hasNext()) {CachePutRequest cachePutRequest = (CachePutRequest)var8.next();cachePutRequest.apply(cacheValue);}this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;}

6. 本文源码

使用Redis的过程中还会有很多问题,比如缓存数据一致性,缓存数据持久化,内存淘汰机制,缓存雪崩等等等,在面试的时候也经常会用到,博主整理了一份Redis常见的面试,感兴趣的朋友可以看下:

【面试1v1实景模拟】Redis面试官会怎么提问?

本文源码:https://github.com/hack-feng/maple-product/

其中maple-redis模块即为本文的Demo源码。需要的朋友可以看下。

感兴趣的朋友可以帮忙点个star⭐⭐⭐⭐⭐后续会有更多Java相关的集成Demo,让我来做你的百宝袋吧。

🐾我是笑小枫,全网皆可搜的【笑小枫】

相关文章:

SpringBoot 使用 Cache 集成 Redis做缓存保姆教程

1. 项目背景 Spring Cache是Spring框架提供的一个缓存抽象层&#xff0c;它简化了缓存的使用和管理。Spring Cache默认使用服务器内存&#xff0c;并无法控制缓存时长&#xff0c;查找缓存中的数据比较麻烦。 因此Spring Cache支持将缓存数据集成到各种缓存中间件中。本文已常…...

Delphi+SQL Server实现的(GUI)户籍管理系统

1.项目简介 本项目是一个户籍管理系统&#xff0c;用于记录住户身份信息&#xff0c;提供新户登记&#xff08;增加&#xff09;、户籍变更&#xff08;修改&#xff09;、户籍注销&#xff08;删除&#xff09;、户籍查询、曾用名查询、迁户记录查询以及创建备份、删除备份共8…...

Ungoogled Chromium127 编译指南 MacOS篇(七)- 安装依赖包

1. 引言 在获取了 Ungoogled Chromium 的源代码之后&#xff0c;我们需要安装所有必要的依赖包。这些依赖包对于成功编译 Chromium 至关重要。本文将指导您完成所有必需软件包的安装。 2. 依赖包安装 2.1 使用 Homebrew 安装基础依赖 # 安装 Ninja 构建系统 brew install n…...

开放词汇检测新晋SOTA:地瓜机器人开源DOSOD实时检测算法

在计算机视觉领域&#xff0c;目标检测是一项关键技术&#xff0c;旨在识别图像或视频中感兴趣物体的位置与类别。传统的闭集检测长期占据主导地位&#xff0c;但近年来&#xff0c;开放词汇检测&#xff08;Open-Vocabulary Object Detection-OVOD 或者 Open-Set Object Detec…...

json dump避免转义字符反斜杠

笔者在将json序列化到文件时&#xff0c;发现内容包含了反斜杠&#xff1a; [{"video": "MSRVTT-QA\/video_features\/7010.mp4","id": 170859},... ]解决办法是使用ensure_asciiFalse&#xff1a; json.dump(result_items, f, ensure_asciiFa…...

PL/SQL语言的正则表达式

PL/SQL语言的正则表达式详解 在现代软件开发中&#xff0c;数据处理和文本处理是至关重要的环节之一。尤其是在数据库操作中&#xff0c;使用正则表达式来处理字符串数据能大幅提高效率和灵活性。PL/SQL&#xff08;Procedural Language/SQL&#xff09;是Oracle数据库的过程性…...

C/C++中头文件time

在C/C中&#xff0c;<ctime>头文件提供了处理时间和日期的函数&#xff0c;这些函数允许你获取当前时间、计算时间差、格式化时间字符串等。以下是一些<ctime>头文件中常用函数的详细介绍和使用示例&#xff1a; time()&#xff1a;获取当前时间。 time_t currentT…...

【.NET】Kafka消息队列介绍,使用Confluent.Kafka集成Kafka消息队列

一、Kafka介绍 kafka是一种高吞吐量、分布式、可扩展的消息中间件系统&#xff0c;最初由LinkedIn公司开发。随着不断的发展&#xff0c;在最新的版本中它定义为分布式的流处理平台&#xff0c;现在在大数据应用中也是十分广泛。 它可以处理大量的实时数据流&#xff0c;被广…...

图像处理|膨胀操作

在图像处理领域&#xff0c;形态学操作是一种基于图像形状的操作&#xff0c;用于分析和处理图像中对象的几何结构。**膨胀操作&#xff08;Dilation&#xff09;**是形态学操作的一种&#xff0c;它能够扩展图像中白色区域&#xff08;前景&#xff09;或减少黑色区域&#xf…...

kali安装

2024年最新kali Linux安装教程&#xff08;超详细&#xff0c;图文并茂&#xff09;_kali安装-CSDN博客 【2024年最新版】Kali安装详细教程-CSDN博客 Kali Linux 安装过程 超详细&#xff08;图文并茂&#xff0c;通用版&#xff09;-腾讯云开发者社区-腾讯云...

【Python3】异步操作 redis

aioredis 在高版本已经不支持了&#xff0c; 不要用 代码示例 redis 连接池异步操作redis以及接口 import asyncio from sanic import Sanic from sanic.response import json import redis.asyncio as redis from redis.asyncio import ConnectionPool# 创建 Sanic 应用 app…...

C++ vtordisp的应用场景

文章目录 问题代码1. 基本概念回顾2. 应用场景虚继承与虚函数并存的类层次结构 3. 编译器相关考虑 问题代码 #include <iostream> using namespace std;class base { public:base() {}virtual void show() { cout << "base:: show"<<endl; } priv…...

花生好坏缺陷识别数据集,7262张图片,支持yolo,coco json,pasical voc xml格式的标注,识别准确率在95.7%

花生好坏缺陷识别数据集,7262张图片&#xff0c;支持yolo&#xff0c;coco json&#xff0c;pasical voc xml格式的标注&#xff0c;识别准确率在95.7% 数据集分割 训练组87&#xff05; 6353图片 有效集8% 606图片 测试集4% 303图片 预处理 自动定…...

递归构建树菜单节点

一、获取所有分类上下级信息 /*** 获取所有分类上下级信息*/ public R<List<ResearchTypeTreeVO>> getTypeTreeList(){//获取所有分类数据List<ResearchTypeVO> list ibResearchTypeService.getSuperList(null);List<ResearchTypeTreeVO> researchTy…...

物联网无线芯片模组方案,设备智能化交互升级,ESP32-C3控制应用

无线交互技术的核心在于实现设备之间的无缝连接和数据传输。在智能家居系统中&#xff0c;各种智能设备如智能灯泡、智能插座、智能门锁等&#xff0c;都通过无线网络相互连接&#xff0c;形成一个互联互通的生态。 用户可以通过语音助手、手机APP或其他智能终端&#xff0c;远…...

【Unity万人同屏插件】使用手册 保姆级教程 GPU动画 Jobs多线程渲染

【万人同屏插件】 基于Dots技术&#xff0c;高性能实现3D、2D Spine渲染、海量单位锁敌/碰撞检测。同时通过自定义BRG渲染器绕过对Entities包的依赖&#xff0c;也就是不用写ECS代码即可拥有Entities Graphics的高性能渲染&#xff0c;使用传统开发方式BRG接管Renderer组件。 …...

结合前端的响应式开发深入理解设备像素比

前端响应式开发中设备像素比是一个绕不开的概念&#xff0c;彻底理解这个概念&#xff0c;我们首先要梳理清楚屏幕设备的物理像素&#xff08;Physical Pixel&#xff09;、逻辑像素&#xff08;Logical Pixel&#xff09;以及css像素&#xff08;px&#xff09;之前的区别和联…...

QT c++ 按钮 样式 设置按下和松开的背景颜色

上一篇文章&#xff0c;需要自定义类&#xff0c;本文使用样式设置按下和松开的背景颜色。 1.头文件 #ifndef WIDGET_H #define WIDGET_H #include <QWidget> //#include "CustomButton.h" #include <QVBoxLayout> //#include <QLinearGradient>…...

解决Docker冲突问题

错误&#xff1a;docker-ce-cli conflicts with 2:docker-1.13.1-210.git7d71120.el7.centos.x86_64 错误&#xff1a;docker-ce conflicts with 2:docker-1.13.1-210.git7d71120.el7.centos.x86_64 您可以尝试添加 --skip-broken 选项来解决该问题 您可以尝试执行&#xff1a;…...

C++之闭散列哈希表

目录 unordered_set和unordered_map 哈希概念 哈希表基本结构 哈希冲突 线性探测​编辑 二次探测 前几期我们学习了红黑树和红黑树的模拟实现&#xff0c;最终使用红黑树封装了map和set。本期开始我们将学习下一个重要的知识点---哈希表&#xff0c;最终使用哈希表封装u…...

微信小程序map组件所有markers展示在视野范围内

注意&#xff1a;使用include-points属性不生效&#xff0c;要通过createMapContext实现 <template><view class"map-box"><map id"map" class"map" :markers"markers" :enable-traffic"true" :enable-poi&…...

Ubuntu平台虚拟机软件学习笔记

Ubuntu平台上常见虚拟机软件 VirtualBox [Download]KVM/QEMU 1. VirtualBox 1.1 查看安装版本 VBoxManage -V2. KVM/QEMU KVM: Kernel-based Virtual Machine QEMU: Quick EMUlator 通义千问&#xff1a; virt-manager 既不是QEMU也不是KVM&#xff0c;而是用于管理和创建…...

EasyExcel数据的导入导出

1.easyExcel简介 EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。 他能让你在不用考虑性能、内存的等因素的情况下&#xff0c;快速完成Excel的读、写等功能。 EasyExcel 的主要特点如下&#xff1a; 1、高性能&#xff1a;EasyExcel 采用了异…...

CSS Grid 布局全攻略:从基础到进阶

文章目录 一.Grid 是什么二.示例代码1. 基础使用 - 固定宽高2.百分百宽高3.重复设置-repeat4.单位-fr5.自适应6.间距定义其他 一.Grid 是什么 CSS 中 Grid 是一种强大的布局方式&#xff0c;它可以同时处理行和列 Grid 和Flex有一些类似&#xff0c;都是由父元素包裹子元素使用…...

C语言的语法

C语言的语法与应用探讨 C语言作为一种高效的程序设计语言&#xff0c;自1970年代问世以来&#xff0c;一直在科学计算、系统编程、嵌入式系统等领域中扮演着重要角色。本文将深入探讨C语言的基本语法、数据结构、控制结构以及其在实际应用中的重要性。 一、C语言基础 1.1 数…...

Go中的context 包使用详解

context 包在 Go 中非常重要&#xff0c;特别是在处理并发和超时的场景下&#xff0c;它能让你在多个 goroutine 之间传递取消信号、超时控制或其他控制信息。context 是 Go 并发模型中的一个重要工具&#xff0c;尤其适用于 HTTP 请求、数据库操作、分布式系统等场景。 1. 基…...

通俗易懂之线性回归时序预测PyTorch实践

线性回归&#xff08;Linear Regression&#xff09;是机器学习中最基本且广泛应用的算法之一。它不仅作为入门学习的经典案例&#xff0c;也是许多复杂模型的基础。本文将全面介绍线性回归的原理、应用&#xff0c;并通过一段PyTorch代码进行实践演示&#xff0c;帮助读者深入…...

机器学习模型评估指标

模型的评估指标是衡量一个模型应用于对应任务的契合程度&#xff0c;常见的指标有&#xff1a; 准确率&#xff08;Accuracy&#xff09;: 正确预测的样本数占总样本数的比例。适用于类别分布均衡的数据集。 精确率&#xff08;Precision&#xff09;: 在所有被预测为正类的样…...

嵌入式软件C语言面试常见问题及答案解析(三)

嵌入式软件C语言面试常见问题及答案解析(三) 上一篇已经足够长了,再长也就有点不礼貌了,所以在这儿继续来总结分享那个面试中遇到的题目,文中的问题和提供的答案或者代码均代表个人的理解,如有不合理或者错误的地方,欢迎大家批评指正。 本文中题目列表 1. 编码实现子串定…...

LeetCode:165. 比较版本号(双指针 Java)

目录 165. 比较版本号 题目描述&#xff1a; 实现代码与解析&#xff1a; 双指针 原理思路&#xff1a; 165. 比较版本号 题目描述&#xff1a; 给你两个 版本号字符串 version1 和 version2 &#xff0c;请你比较它们。版本号由被点 . 分开的修订号组成。修订号的值 是它…...

Golang中遇到“note module requires Go xxx”后的解决方案,不升级Go版本!

前几天&#xff0c;需要对一个两年前写的项目添加点儿新功能&#xff0c;需要用到一个 Http 客户端包&#xff0c;于是就用了 https://github.com/go-resty/resty 这个插件包。 我先是直接在项目根目录下执行了以下包的安装命令&#xff1a; go get -v github.com/go-resty/res…...

ubuntu编译ijkplayer,支持rmvb以及mkv

1. 准备环境 sudo apt-get update apt install gcc yasm cmake python p7zip-full vim pkg-config autoconf automake build-essential dos2unix mercurial cmake-curse-gui -y apt-get -y --force-yes install libass-dev libtheora-dev libtool libva-dev libvdpau-dev libv…...

mysql之sql的优化方案(重点)

1、全字段匹配是最棒的 假如一个Staffs 这个表&#xff0c;将 name,age ,pos 组合成了一个联合索引&#xff0c;在where条件下&#xff0c;能够使用到的索引越多越好。 EXPLAIN SELECT * FROM staffs WHERE NAME July; EXPLAIN SELECT * FROM staffs WHERE NAME July AND age…...

使用Qt实现json数据的格式检测并序列化输出 Qt5.4.0环境

问题&#xff1a; 使用 Qt 实现 JSON 数据的格式检测&#xff0c;并输出各个键值 代码&#xff1a; widget.h #include <QWidget> #include <QJsonDocument> /*序列化 反序列化(F1查看帮助文档)*/ #include <QJsonObject> /*利用对象代表了一段json数据*/ …...

Internet协议原理

文章目录 考试说明Chapter 0: 本书介绍Chapter 1: Introduction And Overview 【第1章&#xff1a;引言与概述】Chapter 2: Overview Of Underlying Network Technologies 【第2章&#xff1a;底层网络技术的回顾】Chapter 3: Internetworking Concept And Architectural Model…...

国标GB28181-2022视频平台EasyGBS小知识:局域网ip地址不够用怎么解决?

在局域网中&#xff0c;IP地址不足的问题通常不会在小型网络中出现&#xff0c;但在拥有超过255台设备的大型局域网中&#xff0c;就需要考虑如何解决IP地址不够用的问题了。 在企业局域网中&#xff0c;经常会出现私有IP地址如192.168.1.x到192.168.1.255不够用的情况。由于0…...

CentOS 使用 yum 方式安装 Nginx

CentOS 使用 yum 方式安装 Nginx 文章目录 CentOS 使用 yum 方式安装 Nginx1、Nginx 安装前提条件步骤 1&#xff1a;更新系统软件包步骤 2&#xff1a;查看 Nginx 相关的软件包步骤 3&#xff1a;安装 Nginx步骤 4&#xff1a;启动并启用 Nginx步骤 5&#xff1a;验证 Nginx 是…...

Spring Boot教程之五十一:Spring Boot – CrudRepository 示例

Spring Boot – CrudRepository 示例 Spring Boot 建立在 Spring 之上&#xff0c;包含 Spring 的所有功能。由于其快速的生产就绪环境&#xff0c;使开发人员能够直接专注于逻辑&#xff0c;而不必费力配置和设置&#xff0c;因此如今它正成为开发人员的最爱。Spring Boot 是…...

消息队列:原理、问题与设计全解析

1.如何保证消息的顺序性 保证消息顺序性通常是在分布式系统或网络通信中遇到的一个挑战。以下是几种常见的方法来确保消息的顺序性&#xff1a; 单生产者单消费者模型&#xff1a; 如果系统设计为只有一个生产者和一个消费者&#xff0c;那么保持消息顺序相对简单&#xff0c;…...

成功!QT 5.15.2编译mysql驱动

首选要说明&#xff0c;5.15与6.7编译驱动是完全不同的。搞错了永远编译不出来。 参考 主要是参考安装QT&#xff0c;安装mysql等。 编译成功&#xff01;QT/6.7.2/Creator编译Windows64 MySQL驱动(MSVC版)_mingw编译qt6.7-CSDN博客 复制mysql的include和lib到一个方便的目…...

【玩转全栈】----Django连接MySQL

阅前先赞&#xff0c;养好习惯&#xff01; 目录 1、ORM框架介绍 选择建议 2、安装mysqlclient 3、创建数据库 4、修改settings&#xff0c;连接数据库 5、对数据库进行操作 创建表 删除表 添加数据 删除数据 修改&#xff08;更新&#xff09;数据&#xff1a; 获取数据 1、OR…...

【Spring Boot】Spring AOP 快速上手指南:开启面向切面编程新旅程

前言 &#x1f31f;&#x1f31f;本期讲解关于spring aop的入门介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话不…...

力扣--54.螺旋矩阵

题目 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 提示&#xff1a; m matrix.length n matrix[i].length 1 < m, n < 10 -100 < matrix[i][j] < 100代码 class Solution { public List spiralOr…...

【Uniapp-Vue3】image媒体组件属性

如果我们想要在页面上展示图片就需要使用到image标签。 这部分最重要的是图片的裁剪&#xff0c;图片的裁剪和缩放属性&#xff1a; mode 图片裁剪、缩放的模式 默认值是scaleToFill 我将用两张图片对属性进行演示&#xff0c;一张是pic1.jpg&#xff08;宽更长&#xf…...

Ubuntu上安装Apache Spark

在Ubuntu上安装Apache Spark的步骤如下&#xff1a; 1. 安装Java Spark是用Scala编写的&#xff0c;并且依赖Java。因此&#xff0c;首先需要安装Java。 安装OpenJDK 8&#xff08;或更高版本&#xff09; 执行以下命令安装OpenJDK&#xff1a; sudo apt update sudo apt …...

Nginx入门笔记

Nginx入门笔记 一、Nginx基本概念二、代理1、正向代理2、反向代理 三、准备工作1、CentOS 7安装nginx&#xff08;1&#xff09;. 安装必要的依赖&#xff08;2&#xff09;下载nginx&#xff08;3&#xff09;编译安装&#xff08;4&#xff09;编译并安装 Nginx(5)启动nginx …...

HTML5 滑动效果(Slide In/Out)详解

HTML5 滑动效果&#xff08;Slide In/Out&#xff09;详解 滑动效果&#xff08;Slide In/Out&#xff09;是一种常见的动画效果&#xff0c;使元素从一侧滑入或滑出&#xff0c;增强页面的动态感和用户体验。以下是滑动效果的详细介绍及实现示例。 1. 滑动效果的特点 动态视…...

unity学习8:unity的基础操作 和对应shortcut

目录 1 unity的基础操作的工具&#xff0c;就在scene边上 1.1 对应shortcut快捷键 2 物体的重置/ 坐标归到0附近 3 F&#xff1a;快速找到当前gameobject 4 Q&#xff1a;小手和眼睛&#xff0c;在场景中移动 5 W&#xff1a;十字箭头&#xff0c;移动gameobject 6 …...

计算机网络 (32)用户数据报协议UDP

前言 用户数据报协议&#xff08;UDP&#xff0c;User Datagram Protocol&#xff09;是计算机网络中的一种重要传输层协议&#xff0c;它提供了无连接的、不可靠的、面向报文的通信服务。 一、基本概念 UDP协议位于传输层&#xff0c;介于应用层和网络层之间。它不像TCP那样提…...

java内存区域 - 栈

目录 java内存区域 - 栈1. Java虚拟机栈的组成2. 栈帧中的详细内容2.1 局部变量表2.2 操作数栈2.3 动态链接2.4 方法返回地址2.5 附加信息 3. JVM栈的生命周期4. 示例解析 - 运行时的栈帧分布5. 栈中的异常6.栈配置7.本地方法栈 java内存区域 - 栈 在JDK11中&#xff0c;JVM栈…...