SpringBoot 使用 Cache 集成 Redis做缓存保姆教程
1. 项目背景
Spring Cache是Spring框架提供的一个缓存抽象层,它简化了缓存的使用和管理。Spring Cache默认使用服务器内存,并无法控制缓存时长,查找缓存中的数据比较麻烦。
因此Spring Cache支持将缓存数据集成到各种缓存中间件中。本文已常用的Redis作为缓存中间件作为示例,详细讲解项目中如何使用Cache提高系统性能。
2. Spring Cache介绍
Spring Cache是Spring框架提供的一种缓存解决方案,基于AOP原理,实现了基于注解的缓存功能,只需要简单地加一个注解就能实现缓存功能,对业务代码的侵入性很小。
使用Spring Cache的方法很简单,只需要在方法上添加注解即可实现将方法返回数据存入缓存,以及清理缓存等注解的使用。
2.1 主要特点
- 统一的缓存抽象:Spring Cache为应用提供了一种统一的缓存抽象,可以轻松集成各种缓存提供者(如Ehcache、Redis、Caffeine等),使用统一的API。
- 注解驱动:Spring Cache通过简单的注解配置,如
@Cacheable
、@CachePut
、@CacheEvict
等,可以快速实现缓存功能,而无需处理底层缓存逻辑。 - 灵活性和扩展性:Spring Cache允许根据业务需求自定义缓存策略,如缓存的失效时间、缓存的淘汰策略等。同时,它也提供了
CacheManager
接口和Cache
接口,可以实现降低对各种缓存框架的耦合。
2.2 常用注解
@EnableCaching
- 作用:开启Spring的缓存注解支持。
- 使用场景:在配置类上添加此注解,以启用Spring Cache的注解处理功能。
- 注意:此注解本身并不提供缓存实现,而是允许你使用
@Cacheable
、@CachePut
、@CacheEvict
等注解来定义缓存行为。
@Cacheable
- 作用:在方法执行前检查缓存,如果缓存中存在数据则直接返回,否则执行方法并将结果缓存。
- value:指定缓存的名称(或名称数组)。缓存名称与
CacheManager
中配置的缓存对应。 - key:用于生成缓存键的表达式(可选)。如果不指定,则默认使用方法的参数值作为键。
- condition:条件表达式(可选),用于决定是否执行缓存操作。
- unless:否定条件表达式(可选),用于在方法执行后决定是否缓存返回值。
@Cacheable注解配置参数说明
-
value/cacheNames:
- 用于指定缓存的名称(或名称数组),缓存名称作为缓存key的前缀。这是缓存的标识符,用于在
CacheManager
中查找对应的缓存。 value
和cacheNames
是互斥的,即只能指定其中一个。
- 用于指定缓存的名称(或名称数组),缓存名称作为缓存key的前缀。这是缓存的标识符,用于在
-
key:
- 用于生成缓存键的表达式。这个键用于在缓存中唯一标识存储的值。
- 如果不指定
key
,则默认使用方法的参数值(经过某种转换)作为键。 - 可以使用Spring Expression Language(SpEL)来编写
key
表达式,以实现动态键的生成。
-
keyGenerator:
- 指定一个自定义的键生成器(实现 org.springframework.cache.interceptor.KeyGenerator 接口的类),用于生成缓存的键。与 key 属性互斥,二者只能选其一。
- 如果同时指定了
key
和keyGenerator
,则会引发异常,因为它们是互斥的。 - 开发者可以编写自己的
KeyGenerator
实现,并将其注册到Spring容器中,然后在@Cacheable
注解中引用。
-
cacheManager:
CacheManager
表示缓存管理器,通过缓存管理器可以设置缓存过期时间。- 用于指定要使用的
CacheManager
。这是一个可选参数,通常不需要显式指定,因为Spring会默认使用配置的CacheManager。
- 如果系统中配置了多个
CacheManager
,则需要通过此参数指定使用哪一个。
-
cacheResolver:
- 缓存解析器,用于解析缓存名称并返回相应的
Cache
对象。这也是一个可选参数。 - 类似于
cacheManager
,如果系统中配置了多个缓存解析逻辑,可以通过此参数指定使用哪一个。
- 缓存解析器,用于解析缓存名称并返回相应的
-
condition:
- 条件表达式,用于决定是否执行缓存操作。这是一个可选参数。
- 条件表达式使用SpEL编写,如果表达式返回
true
,则执行缓存操作;否则不执行。
-
unless:
- 否定条件表达式,用于在方法执行后决定是否缓存返回值。这也是一个可选参数。
- 与
condition
类似,unless
也使用SpEL编写,但它是在方法执行后才进行评估的。 - 如果
unless
表达式返回true
,则不缓存返回值;否则缓存。
-
sync:
- 是否使用异步模式进行缓存操作。这是一个可选参数,通常不需要显式指定。
- 在多线程环境中,如果多个线程同时请求相同的数据并触发缓存操作,使用异步模式可以避免线程阻塞和重复计算。
@Cacheable
注解的这些参数是互斥或相互关联的,例如value
和cacheNames
不能同时指定,key
和keyGenerator
也不能同时指定。此外,cacheManager
和cacheResolver
也是互斥的,因为它们都用于指定缓存的解析和管理方式。
对于前两个注解的应用:
@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"})
表示方法的结果将同时缓存到cache1
和cache2
中。 - 与
@CacheConfig
结合使用:如果类上使用了@CacheConfig
注解,并且指定了cacheNames
属性,那么类中的方法在使用@Cacheable
时可以省略value
属性,直接使用类级别的缓存配置。
@CacheEvict
- 作用:从缓存中删除数据。
- value:指定要删除的缓存的名称(或名称数组)。
- key:用于指定要删除的缓存键(可选)。如果不指定,则默认使用方法的参数值作为键。
- allEntries:布尔值,指定是否删除缓存中的所有条目(而不是仅删除与指定键匹配的条目)。
- beforeInvocation:布尔值,指定是否在方法执行之前删除缓存(默认为
false
,即在方法执行之后删除)。
@CachePut
- 作用:更新缓存中的数据,无论方法是否成功执行,都会将结果放入缓存。
- value、key、condition、unless:与
@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
对象就id
、name
两个字段,大家随意~
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
根据条件选择不同的缓存管理器进行缓存数据。
简单附几张测试截图吧
第一次查询,没有缓存截图:
后续查询走缓存的截图
redis缓存数据格式:
redis缓存数据详情:
4. SpEL在Spring Cache中的应用
4.1 SpEL概述
SpEL是Spring框架提供的一种功能强大的表达式语言,它能够在运行时查询和操作对象图。SpEL的语法简洁,支持方法调用、字符串模板、集合操作、逻辑运算等复杂功能,使得在Spring配置和代码中能够更轻松地处理复杂的逻辑和数据结构。
4.2 SpEL应用
-
动态生成缓存键
- 在Spring Cache中,缓存键(Key)用于在缓存中唯一标识数据。通过使用SpEL表达式,可以根据方法参数、返回值等动态生成缓存键。
- 例如,在@Cacheable注解中,可以使用key属性配合SpEL表达式来指定缓存键的生成规则。
-
条件缓存
- Spring Cache允许通过condition属性来指定缓存的条件。当条件满足时,才会执行缓存操作(如缓存数据或移除缓存)。
-
除非条件
- unless属性与condition属性类似,但它用于指定不执行缓存操作的条件。
- 当unless条件满足时,即使方法被调用,其结果也不会被缓存。
- unless属性同样支持SpEL表达式。
4.3 SpEL表达式在Spring Cache中的常用变量
-
#参数名:
- 表示方法参数。可以通过参数名来引用方法参数的值。
- 例如,#param1表示第一个参数的值。
-
#result:
- 表示方法的返回值。在@CachePut和@CacheEvict注解中,可以使用#result来引用方法的返回值。
-
#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框架提供的一个缓存抽象层,它简化了缓存的使用和管理。Spring Cache默认使用服务器内存,并无法控制缓存时长,查找缓存中的数据比较麻烦。 因此Spring Cache支持将缓存数据集成到各种缓存中间件中。本文已常…...
R数据分析:多分类问题预测模型的ROC做法及解释
有同学做了个多分类的预测模型,结局有三个类别,做的模型包括多分类逻辑回归、随机森林和决策树,多分类逻辑回归是用ROC曲线并报告AUC作为模型评估的,后面两种模型报告了混淆矩阵,审稿人就提出要统一模型评估指标。那么肯定是统一成ROC了,刚好借这个机会给大家讲讲ROC在多…...
数据结构与算法之二叉树: LeetCode 654. 最大二叉树 (Ts版)
最大二叉树 https://leetcode.cn/problems/maximum-binary-tree/ 描述 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点,其值为 nums 中的最大值递归地在最大值 左边 的 子数组前缀上 构建左子树递归地在最大值…...
Linux 容器漏洞
定义:Linux 容器漏洞是指在容器技术(如 Docker、LXC 等)运行环境中存在的安全弱点。这些漏洞可能存在于容器镜像本身、容器运行时(如 runc)、容器编排工具(如 Kubernetes)或者容器与主机之间的交…...
file与io流(1)
-1- java.io.File类的使用 (1) 概述 File类及本章下的各种流,都定义在java.io包下。一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录(俗称文件夹),与平台无关。(体会万事万物皆…...
忘记了PDF文件的密码,怎么办?
PDF文件可以加密,大家都不陌生,并且大家应该也都知道PDF文件有两种密码,一个打开密码、一个限制编辑密码,因为PDF文件设置了密码,那么打开、编辑PDF文件就会受到限制。忘记了PDF密码该如何解密? PDF和offi…...
Linux权限管理(用户和权限之间的关系)
Linux系列 文章目录 Linux系列一、Linux下用户类型二、普通权限的基本概念2.1、Linux中权限的类别2.2、Linux中权限对应的三种身份2.3、文件权限的标识 三、文件权限设置四、修改文件属主和属组4.1、chown修改文件的属主4.2、修改所属组 五、文件掩码六、目录权限 一、Linux下用…...
Python Selenium库入门使用,图文详细。附网页爬虫、web自动化操作等实战操作。
文章目录 前言1 创建conda环境安装Selenium库2 浏览器驱动下载(以Chrome和Edge为例)3 基础使用(以Chrome为例演示)3.1 与浏览器相关的操作3.1.1 打开/关闭浏览器3.1.2 访问指定域名的网页3.1.3 控制浏览器的窗口大小3.1.4 前进/后…...
【Uniapp-Vue3】使用defineExpose暴露子组件的属性及方法
如果我们想要让父组件访问到子组件中的变量和方法,就需要使用defineExpose暴露: defineExpose({ 变量 }) 子组件配置 父组件配置 父组件要通过onMounted获取到子组件的DOM 传递多个属性和方法 子组件 父组件...
【多模态LLM】英伟达NVLM多模态大模型训练细节和数据集
前期笔者介绍了OCR-free的多模态大模型,可以参考:【多模态&文档智能】OCR-free感知多模态大模型技术链路及训练数据细节,其更偏向于训练模型对于密集文本的感知能力。本文看一看英伟达出品的多模态大模型NVLM-1.0系列,虽然暂未…...
HTTP详解——HTTP基础
HTTP 基本概念 HTTP 是超文本传输协议 (HyperText Transfer Protocol) 超文本传输协议(HyperText Transfer Protocol) HTTP 是一个在计算机世界里专门在 两点 之间 传输 文字、图片、音视频等 超文本 数据的 约定和规范 1. 协议 约定和规范 2. 传输 两点之间传输…...
MySQL教程之:输入查询
如上一节所述,确保您已连接到服务器。这样做本身不会选择任何要使用的数据库,但没关系。在这一点上,了解一下如何发出查询比直接创建表、加载数据和从中检索数据更重要。本节介绍输入查询的基本原则,使用几个查询,您可…...
docker+ffmpeg+nginx+rtmp 拉取摄像机视频
1、构造程序容器镜像 app.py import subprocess import json import time import multiprocessing import socketdef check_rtmp_server(host, port, timeout5):try:with socket.create_connection((host, port), timeout):print(f"RTMP server at {host}:{port} is avai…...
不同音频振幅dBFS计算方法
1. 振幅的基本概念 振幅是描述音频信号强度的一个重要参数。它通常表示为信号的幅度值,幅度越大,声音听起来就越响。为了更好地理解和处理音频信号,通常会将振幅转换为分贝(dB)单位。分贝是一个对数单位,能…...
【17. 电话号码的字母组合 中等】
题目: 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例 1: 输入:digits “23”…...
数据结构初阶---排序
一、排序相关概念与运用 1.排序相关概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的…...
【从0-1实现一个前端脚手架】
目录 介绍为什么需要脚手架?一个脚手架应该具备哪些功能? 脚手架实现初始化项目相关依赖实现脚手架 发布 介绍 为什么需要脚手架? 脚手架本质就是一个工具,作用是能够让使用者专注于写代码,它可以让我们只用一个命令…...
AI文章管理系统(自动生成图文分发到分站)
最近帮一个网上的朋友做了一套AI文章生成系统。他的需求是这样: 1、做一个服务端转接百度文心一言的生成文章的API接口。 2、服务端能注册用户,用户在服务端注册充值后可以获取一个令牌,这个令牌填写到客户端,客户端就可以根据客…...
【Leetcode 每日一题】3270. 求出数字答案
问题背景 给你三个 正 整数 n u m 1 num_1 num1, n u m 2 num_2 num2 和 n u m 3 num_3 num3。 数字 n u m 1 num_1 num1, n u m 2 num_2 num2 和 n u m 3 num_3 num3 的数字答案 k e y key key 是一个四位数,定义如下&…...
基于单片机的无线气象仪系统设计(论文+源码)
1系统方案设计 如图2.1所示为无线气象仪系统设计框架。系统设计采用STM32单片机作为主控制器,结合DHT11温湿度传感器、光敏传感器、BMP180气压传感器、PR-3000-FS-N01风速传感器实现气象环境的温度、湿度、光照、气压、风速等环境数据的检测,并通过OLED1…...
【数据库】Mysql精简回顾复习
一、概念 数据库(DB):数据存储的仓库数据库管理系统(DBMS):操纵和管理数据库的大型软件SQL:操作关系型数据库的编程语言,是一套标准关系型数据库(RDBMS)&…...
深入理解 HTTP 的 GET、POST 方法与 Request 和 Response
HTTP 协议是构建 Web 应用的基石,GET 和 POST 是其中最常用的请求方法。无论是前端开发、后端开发,还是接口测试,对它们的深入理解都显得尤为重要。在本文中,我们将介绍 GET 和 POST 方法,以及 Request 和 Response 的…...
MySQL 中联合索引相比单索引性能提升在哪?
首先我们要清楚所以也是要占用磁盘空间的,随着表中数据量越来越多,索引的空间也是随之提升的,因而单表不建议定义过多的索引,所以使用联合索引可以在一定程度上可以减少索引的空间占用其次,使用联合索引的情况下&#…...
第34天:安全开发-JavaEE应用反射机制攻击链类对象成员变量方法构造方法
时间轴: Java反射相关类图解: 反射: 1、什么是 Java 反射 参考: https://xz.aliyun.com/t/9117 Java 提供了一套反射 API ,该 API 由 Class 类与 java.lang.reflect 类库组成。 该类库包含了 Field 、 Me…...
C++笔记之数据单位与C语言变量类型和范围
C++笔记之数据单位与C语言变量类型和范围 code review! 文章目录 C++笔记之数据单位与C语言变量类型和范围一、数据单位1. 数据单位表:按单位的递增顺序排列2. 关于换算关系的说明3. 一般用法及注意事项4. 扩展内容5. 理解和使用建议二、C 语言变量类型和范围基本数据类型标准…...
算法-拆分数位后四位数字的最小和
力扣题目2160. 拆分数位后四位数字的最小和 - 力扣(LeetCode) 给你一个四位 正 整数 num 。请你使用 num 中的 数位 ,将 num 拆成两个新的整数 new1 和 new2 。new1 和 new2 中可以有 前导 0 ,且 num 中 所有 数位都必须使用。 …...
Python 管理 GitHub Secrets 和 Workflows
在现代软件开发中,自动化配置管理变得越来越重要。本文将介绍如何使用 Python 脚本来管理 GitHub 仓库的 Secrets 和 Workflows,这对于需要频繁更新配置或管理多个仓库的团队来说尤为有用。我们将分三个部分进行讨论:设置 GitHub 权限、创建 GitHub Secret 和创建 GitHub Wo…...
指令的修饰符
指令的修饰符 参考文献: Vue的快速上手 Vue指令上 Vue指令下 Vue指令的综合案例 文章目录 指令的修饰符指令修饰符 结语 博客主页: He guolin-CSDN博客 关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力&…...
C# 正则表达式完全指南
C# 正则表达式完全指南 C#通过 System.Text.RegularExpressions 命名空间提供强大的正则表达式支持。本指南将详细介绍C#中正则表达式的使用方法、性能优化和最佳实践。 1. 基础知识 1.1 命名空间导入 using System.Text.RegularExpressions;1.2 基本使用 public class Re…...
【笔记整理】记录参加骁龙AIPC开发者技术沙龙的笔记
AIoT 首先了解了一个概念叫AIoT,我的理解就是AI IoT 5G,通过AI的发展使得边缘计算、数据整合和处理变得快捷方便,不仅限于传统的云端数据处理,在边缘的IoT设备上也可以进行智能化打造,通过5G的通信能力扩展可以实现…...
wordpress替换头像/个人主页网页设计
Linux Mint 19.2 “Tina” 在 2019 年 8 月 2 日发布,它是一个基于 Ubuntu 18.04 LTS (Bionic Beaver) 的长期支持版本。-- 2daygeek(作者)Linux Mint 19.2 “Tina” 在 2019 年 8 月 2 日发布,它是一个基于 Ubuntu 18.04 LTS (Bi…...
wordpress怎么添加导航/免费行情软件网站下载大全
使用编辑 WinIO程序库允许在32位的Windows应用程序中直接对I/O端口和物理内存进行存取操作。通过使用一种内核模式的设备驱动器和其它几种底层编程技巧,它绕过了Windows系统的保护机制。3工作原理编辑 WinNT/2000/XP下,WinIO函数库只允许被具有管理者权限…...
创建目录 wordpress/公司网页制作流程
作者【黄小斜】文章来源【程序员江湖】黄小斜,斜杠青年,某985硕士,阿里研发工程师,于2018 年秋招拿到 BAT 头条、网易、滴滴等 8 个大厂 offer个人擅长领域 :自学编程、技术校园招聘、软件工程考研Java并发编程一直是J…...
河北省和城乡建设厅网站/求职seo推荐
此块内容参考Ajax文档部分。主要复习内容:1.JavaScript核心对象 2.浏览器BOM对象3.文档对象模型DOM4.常见事件5.Ajax编程(web交互2种方式的对比)6.传统Ajax编程的步骤以及从服务器端返回的数据格式 7.JSON数据格式的转换操作 8.jQuery选择器 9.jQuery的Ajax编程(常见…...
成都房产网安居客/seo关键词推广
linux 日期时间命令how to set linux date and time in commands如何在命令中设置Linux日期和时间 For example, to change date to 14 Nov 2017 11:57:00, the command would be,例如,要将日期更改为2017年11月14日11:57:00, 命令应为: $ …...
福州建设工程造价信息网/seo和sem的区别
ES6中的扩展运算符拷贝问题以及常用的深浅拷贝方法 在ES6中新增了扩展运算符可以对数组和对象进行操作。有时候会遇到数组和对象的拷贝,可能会用到扩展运算符。那么这个扩展运算符到底是深拷贝还是浅拷贝呢? 一.、使用扩展运算符拷贝 首先是下面的代码…...