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

【Redis】Redis 的学习教程(七)之 SpringBoot 集成 Redis

在前几篇文章中,我们详细介绍了 Redis 的一些功能特性以及主流的 java 客户端 api 使用方法。

在当前流行的微服务以及分布式集群环境下,Redis 的使用场景可以说非常的广泛,能解决集群环境下系统中遇到的不少技术问题,在此列举几个使用 Redis 经常用到的功能:

  • 分布式缓存:在分布式的集群架构中,将缓存存储在内存中会出现很多的问题,比如用户回话信息,因为这部分信息需要与其他机器共享,此时利用 Redis 可以很好的解决机器之间数据共享的问题,缓存也是 Redis 中使用最多的场景
  • 分布式锁:在高并发的情况下,我们需要一个锁来防止并发带来的脏数据,Java 自带的锁机制显然对进程间的并发并不好使,此时利用 Redis 的单线程特性,实现分布式锁控制
  • 接口限流:在集群环境下,可以利用 Redis 的分布式自增 ID 功能,精准的统计每个接口在指定时间内的请求次数,利用这个特性,可以定向限制某个接口恶意频刷

当然 Redis 的使用场景并不仅仅只有这么多,还有很多未列出的场景,如发布/订阅,分布锁集合等。

现实中我们大部分的微服务项目,都是基于 SpringBoot 框架进行快速开发,在 SpringBoot 项目中我们应该如何使用 Redis 呢?代码实践如下。

1. 开发环境

  • IDEA:2021.3.3
  • JDK:1.8
  • SpringBoot:2.7.14
  • Maven:3.6.3

咱们通过程序是不能直接连接 Redis,得利用客户端工具才能进行连接。比较常用的有两种:Jedis、Lettuce。

在 springboot 1.5.x 版本的默认的 Redis 客户端是 Jedis 实现的,springboot 2.x 版本中默认客户端是用 lettuce实现的。

既然 LettuceJedis 的都是连接 Redis 的客户端,那么它们有什么区别呢?

  • Jedis 在实现上是直连 Redis Server,多线程环境下非线程安全,除非使用连接池,为每个 Redis 实例增加 物理连接
  • Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个 RedisConnection,它利用 Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序

2. 代码实战

在 SpringBoot 集成的 Redis 时,我这里采用的是 Lettuce

2.1 默认使用 Lettuce

1、引入依赖:

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

2、添加配置:

spring:redis:host: localhostport: 6379password:timeout: 2000s# 配置文件中添加 lettuce.pool 相关配置,则会使用到lettuce连接池lettuce:pool:max-active: 8  # 连接池最大连接数(使用负值表示没有限制) 默认为8max-wait: -1ms # 接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1msmax-idle: 8    # 连接池中的最大空闲连接 默认为8min-idle: 0    # 连接池中的最小空闲连接 默认为 0

2.2 换成 Jedis

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>

jedis 中会引入 commons-pool2 依赖,如果没有引入:

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

添加配置:

spring:redis:host: localhostport: 6379password:timeout: 2000s# 配置文件中添加 jedis.pool 相关配置,则会使用到 jedis 连接池jedis:pool:max-active: 10max-idle: 8min-idle: 0max-wait: 60s

2.3 使用 RedisTemplate 对象操作 Redis

在 SpringBoot 中,是使用 RedisTemplate 对象来操作 Redis 的。

在 Springboot 自动配置原理中,涉及到以下两方面:

  1. SpringBoot 中所有的配置类,都有一个自动配置类。RedisAutoConfiguration
  2. 自动配置类都会绑定一个配置文件 properties。RedisProperties

RedisAutoConfiguration.class

public class RedisAutoConfiguration {public RedisAutoConfiguration() {}@Bean@ConditionalOnMissingBean(name = {"redisTemplate"})@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);}
}

StringRedisTemplate

public class StringRedisTemplate extends RedisTemplate<String, String> {public StringRedisTemplate() {this.setKeySerializer(RedisSerializer.string());this.setValueSerializer(RedisSerializer.string());this.setHashKeySerializer(RedisSerializer.string());this.setHashValueSerializer(RedisSerializer.string());}public StringRedisTemplate(RedisConnectionFactory connectionFactory) {this();this.setConnectionFactory(connectionFactory);this.afterPropertiesSet();}protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {return new DefaultStringRedisConnection(connection);}
}

通过上述看,注入了两个类型的 RedisTemplate 对象:

  1. 如果没有注入名称为 redisTemplate 的 RedisTemplate 对象,则注入 RedisTemplate<Object, Object> 对象
  2. 注入 StringRedisTemplate 对象。而 StringRedisTemplate 对象又是继承 RedisTemplate<String, String> 类的

使用上面两个类型 RedisTemplate 的对象操作 Redis:

@RestController
@RequestMapping("/redis")
public class RedisController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisTemplate<Object, Object> redisTemplate;@GetMapping("/set")public String set() {stringRedisTemplate.opsForValue().set("name", "zzc");redisTemplate.opsForValue().set("age", "zzc");return "set";}
}

调用成功后,我们使用 Redis 客户端工具进行查看:

在这里插入图片描述
发现:

redisTemplate.opsForValue().set("age", "zzc"); 操作的 keyvalue 都变成乱码。

springboot系列——redisTemplate和stringRedisTemplate对比、redisTemplate几种序列化方式比较

通过 debug 源代码知:RedisTemplate<Object, Object> 的 key、value 序列化默认都是 JdkSerializationRedisSerializer,序列化方法如下:

default byte[] serializeToByteArray(T object) throws IOException {ByteArrayOutputStream out = new ByteArrayOutputStream(1024);this.serialize(object, out);return out.toByteArray();
}public void serialize(Object object, OutputStream outputStream) throws IOException {if (!(object instanceof Serializable)) {throw new IllegalArgumentException(this.getClass().getSimpleName() + " requires a Serializable payload but received an object of type [" + object.getClass().getName() + "]");} else {ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeObject(object);objectOutputStream.flush();}
}

将 key、value 进行序列化成 byte 类型,所以,看上去会乱码。(可读性差)

StringRedisTemplate 对象使用 RedisSerializer 序列的

2.4 自定义 RedisTemplate 对象

为了可读性,可以使用 StringRedisTemplate 类,但有一个要求:key、value 都要求是 String 类型。

但这就有一个问题,我们平时用得对象比较多,那又如何存储对象呢?

例如,我们这里的 User 对象:

public class User {private String id;private String userName;private Integer age;// getter/setter
}

由于 RedisTemplate<String, String> 的泛型参数都是 String 类型的,那我们只需要将 Java 对象转换为 String 对象即可:

@Override
public boolean addUser(User user) {redisTemplate.opsForValue().set("user", JSON.toJSONString(user));String strUser = redisTemplate.opsForValue().get("user1");User resultUser = JSON.parseObject(strUser, User.class);return true;
}

存 Redis 之前,将 Java 对象转换为 Json 字符串;读取后,将 Json 字符串转换为 Java 对象。

这样做确实可行,但是,如果要存储的对象较多的话,那岂不是要重复地将 Java 对象转换为 Json 字符串?这样是不是很繁琐?

继续看 RedisAutoConfiguration.class 源码,发现被注入的 RedisTemplate<Object, Object>@ConditionalOnMissingBean(name="redisTemplate") 注解修饰:如果 Spring 容器中有了 RedisTemplate 对象了,这个自动配置的 RedisTemplate 不会实例化。因此我们可以直接自己写个配置类,配置 RedisTemplate。并且,我们更希望 key 是 String 类型,value 是 Object 类型(String、int、对象等类型):

@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);// json 序列化配置Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);jackson2JsonRedisSerializer.setObjectMapper(om);// String 序列化StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// 所有的 key 采用 string 的序列化template.setKeySerializer(stringRedisSerializer);// 所有的 value 采用 jackson 的序列化template.setValueSerializer(jackson2JsonRedisSerializer);// hash 的 key 采用 string 的序列化template.setHashKeySerializer(stringRedisSerializer);// hash 的 value 采用 jackson 的序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}

测试:

@RestController
@RequestMapping("/redis")
public class RedisController {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@GetMapping("/set")public String set() {User user = new User();user.setName("zzc");user.setAge(18);redisTemplate.opsForValue().set("user", user);return "set";}}

2.5 RedisUtil 工具类

@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// ============================Common=============================public void setHashValueSerializer(RedisSerializer serializer) {redisTemplate.setHashValueSerializer(serializer);}/*** 指定缓存失效时间** @author zzc* @date 2023/8/2 11:06* @param key    键* @param time   时间(秒)* @return boolean*/public boolean expire(String key, long time) {try {if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间** @author zzc* @date 2023/8/2 11:07* @param key    键 不能为null* @return long  时间(秒) 返回0代表为永久有效*/public Long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @author zzc* @date 2023/8/2 11:07* @param key      键* @return boolean 存在 false不存在*/public boolean hasKey(String key) {try {return Boolean.TRUE.equals(redisTemplate.hasKey(key));} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存** @author zzc* @date 2023/8/2 11:08* @param key   可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {//springboot2.4后用法redisTemplate.delete(Arrays.asList(key));}}}/*** 获取指定前缀的一系列key* 使用scan命令代替keys, Redis是单线程处理,keys命令在KEY数量较多时,* 操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求** @author zzc* @date 2023/8/2 11:53* @param keyPrefix* @return java.util.Set<java.lang.String>*/public Set<String> keys(String keyPrefix) {String realKey = keyPrefix + "*";try {return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {Set<String> binaryKeys = new HashSet<>();//springboot2.4后用法Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(realKey).count(Integer.MAX_VALUE).build());while (cursor.hasNext()) {binaryKeys.add(new String(cursor.next()));}return binaryKeys;});} catch (Throwable e) {e.printStackTrace();}return null;}/*** 删除指定前缀的一系列key** @author zzc* @date 2023/8/2 11:53* @param keyPrefix*/public void removeAll(String keyPrefix) {try {Set<String> keys = keys(keyPrefix);redisTemplate.delete(keys);} catch (Throwable e) {e.printStackTrace();}}// 执行 lua 脚本public <T> T execute(RedisScript<T> script, List<String> keys, Object... args) {return redisTemplate.execute(script, keys, args);}public boolean convertAndSend(String channel, Object message) {if (!StringUtils.hasText(channel)) {return false;}try {redisTemplate.convertAndSend(channel, message);return true;} catch (Exception e) {e.printStackTrace();}return false;}// ============================String=============================/*** 普通缓存获取** @author zzc* @date 2023/8/2 11:08* @param key                   键* @return java.lang.Object     值*/public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入* @author zzc* @date 2023/8/2 11:09* @param key           键* @param value         值* @return boolean      true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置过期时间** @author zzc* @date 2023/8/2 11:09* @param key     键* @param value   值* @param time    时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return boolean  true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增* @author zzc* @date 2023/8/2 11:10* @param key         键* @param delta       要增加几(大于0)* @return java.lang.Long*/public Long incr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}/*** 递减** @author zzc* @date 2023/8/2 11:11* @param key                 键* @param delta               要减少几(小于0)* @return java.lang.Long*/public Long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}public boolean setNx(String key, Object value) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value));}public boolean setNx(String key, Object value, long time) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS));}public void multiSet(Map<String, Object> map) {redisTemplate.opsForValue().multiSet(map);}public List<Object> multiGet(List<String> keys) {return redisTemplate.opsForValue().multiGet(keys);}// ================================Hash=================================/*** Hash Get* @author zzc* @date 2023/8/2 11:12* @param key                键 不能为null* @param item               项 不能为null* @return java.lang.Object*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取Key对应的所有键值** @author zzc* @date 2023/8/2 11:12* @param key                                                  键* @return java.util.Map<java.lang.Object,java.lang.Object>    对应的多个键值*/public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** Hash Set** @author zzc* @date 2023/8/2 11:13* @param key         键* @param map         对应多个键值* @return boolean    true 成功 false 失败*/public boolean hmset(String key, Map<String, Object> map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** Hash Set 并设置过期时间* @author zzc* @date 2023/8/2 11:13* @param key        键* @param map        对应多个键值* @param time       时间(秒)* @return boolean   true成功 false失败*/public boolean hmset(String key, Map<String, Object> map, long time) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @return true 成功 false失败*/public boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @param time  时间(秒) 注意:如果已存在的hash表有过期时间,这里将会替换原有的过期时间* @return true 成功 false失败*/public boolean hset(String key, String item, Object value, long time) {try {redisTemplate.opsForHash().put(key, item, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的项** @author zzc* @date 2023/8/2 11:38* @param key   键 不能为null* @param item  项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 判断 hash 表中是否有该项的值** @author zzc* @date 2023/8/2 11:38* @param key    键 不能为null* @param item   项 不能为null* @return boolean  true 存在 false不存在*/public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** hash 递增 如果不存在,就会创建一个 并把新增后的值返回** @author zzc* @date 2023/8/2 11:40* @param key    键* @param item   项* @param by     要增加几(大于0)* @return double*/public double hincr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, by);}public Long hincr(String key, String item, long by) {return redisTemplate.opsForHash().increment(key, item, by);}/*** hash 递减** @author zzc* @date 2023/8/2 11:40* @param key    键* @param item   项* @param by     要减少几(小于0)* @return double*/public double hdecr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, -by);}public List<Object> hmultiGet(String key, List<Object> items) {return redisTemplate.opsForHash().multiGet(key, items);}// ============================set=============================/*** 根据key获取Set中的所有值** @author zzc* @date 2023/8/2 11:41* @param key* @return java.util.Set<java.lang.Object>*/public Set<Object> sGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/*** 根据value从一个set中查询,是否存在** @author zzc* @date 2023/8/2 11:41* @param key       键* @param value     值* @return boolean  true 存在 false不存在*/public boolean sHasKey(String key, Object value) {try {return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存** @author zzc* @date 2023/8/2 11:42* @param key       键* @param values    值 可以是多个* @return long     成功个数*/public Long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 将 set 数据放入缓存** @author zzc* @date 2023/8/2 11:42* @param key     键* @param time    时间(秒)* @param values  值 可以是多个* @return long   成功个数*/public Long sSetAndTime(String key, long time, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0) {expire(key, time);}return count;} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 获取set缓存的长度** @author zzc* @date 2023/8/2 11:45* @param key* @return long*/public Long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 移除值为value的** @author zzc* @date 2023/8/2 11:45* @param key    键* @param values 值 可以是多个* @return long  移除的个数*/public Long setRemove(String key, Object... values) {try {return redisTemplate.opsForSet().remove(key, values);} catch (Exception e) {e.printStackTrace();return 0L;}}// ===============================List=================================/*** 获取list缓存的内容** @author zzc* @date 2023/8/2 11:46* @param key      键* @param start    开始* @param end      结束 0 到 -1代表所有值* @return java.util.List<java.lang.Object>*/public List<Object> lGet(String key, long start, long end) {try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取list缓存的长度** @author zzc* @date 2023/8/2 11:47* @param key* @return long*/public Long lGetListSize(String key) {try {return redisTemplate.opsForList().size(key);} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 通过索引 获取list中的值** @author zzc* @date 2023/8/2 11:47* @param key     键* @param index   索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return java.lang.Object*/public Object lGetIndex(String key, long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存* @author zzc* @date 2023/8/2 11:48* @param key       键* @param value     值* @return boolean*/public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @author zzc* @date 2023/8/2 11:48* @param key       键* @param value     值* @param time  时间(秒)* @return boolean*/public boolean lSet(String key, Object value, long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @author zzc* @date 2023/8/2 11:49* @param key        键* @param value      值* @return boolean   时间(秒)*/public boolean lSet(String key, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @author zzc* @date 2023/8/2 11:49* @param key        键* @param value      值* @param time       时间(秒)* @return boolean*/public boolean lSet(String key, List<Object> value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据** @author zzc* @date 2023/8/2 11:51* @param key     键* @param index   索引* @param value   值* @return boolean*/public boolean lUpdateIndex(String key, long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value** @author zzc* @date 2023/8/2 11:51* @param key    键* @param count  移除多少个* @param value  值* @return long  移除的个数*/public Long lRemove(String key, long count, Object value) {try {return redisTemplate.opsForList().remove(key, count, value);} catch (Exception e) {e.printStackTrace();return 0L;}}}

相关文章:

【Redis】Redis 的学习教程(七)之 SpringBoot 集成 Redis

在前几篇文章中&#xff0c;我们详细介绍了 Redis 的一些功能特性以及主流的 java 客户端 api 使用方法。 在当前流行的微服务以及分布式集群环境下&#xff0c;Redis 的使用场景可以说非常的广泛&#xff0c;能解决集群环境下系统中遇到的不少技术问题&#xff0c;在此列举几…...

Vlan和Trunk

文章目录 一、VLAN的定义与背景1. 传统以太网的问题&#xff08;广播域&#xff09;2. 用VLAN隔离广播域3. VLAN的优点与应用 二、VLAN的转发过程举例三、802.1Q标签&#xff1a;帧格式与作用四、VLAN工作原理交换机端口类型AccessTrunkHybrid PVID&#xff08;Port VLAN ID&am…...

java 批量下载将多个文件(minio中存储)压缩成一个zip包

我的需求是将minio中存储的文件按照查询条件查询出来统一压成一个zip包然后下载下来。 思路&#xff1a;针对这个需求&#xff0c;其实可以有多个思路&#xff0c;不过也大同小异&#xff0c;一般都是后端返回流文件前端再处理下载&#xff0c;也有少数是压缩成zip包之后直接给…...

nnUNet v2数据准备及格式转换 (二)

如果你曾经使用过nnUNet V1&#xff0c;那你一定明白数据集的命名是有严格要求的&#xff0c;必须按照特定的格式来进行命名才能正常使用。 这一节的学习需要有数据&#xff0c;如果你有自己的数据&#xff0c;可以拿自己的数据来实验&#xff0c;如果没有&#xff0c;可以用十…...

ant-vue1.78版监听a-modal遮罩层的滚动事件

监听a-modal遮罩层的滚动事件 我们开发过程中经常有遇到监听页面滚动的事件需求&#xff0c;去做一些下拉加载或者是下拉分页的需求&#xff0c;我们直接在vue的生命周期中去绑定事件监听非常的方便&#xff0c;但如果是弹框的遮罩层的滚动监听呢&#xff1f;页面的监听完全是…...

MATLAB中residue函数用法

目录 语法 说明 示例 求解具有实根的部分分式展开式 展开具有复数根和同次分子及分母的分式 展开分子次数高于分母次数的分式 residue函数的功能是部分分式展开&#xff08;部分分式分解&#xff09;。 语法 [r,p,k] residue(b,a) [b,a] residue(r,p,k) 说明 [r,p…...

攻防世界-Caesar

原题 解题思路 没出现什么特殊字符&#xff0c;可能是个移位密码。凯撒密码加密解密。偏移12位就行。...

嵌入式开发-lin总线介绍 一.概述

1.1lin总线定义和历史 LIN总线&#xff08;Local Interconnect Network&#xff09;是一种基于UART/SCI&#xff08;Universal Asynchronous Receiver-Transmitter/Serial Communication Interface&#xff09;的低成本串行通信协议。它主要用于汽车、家电、办公设备等多种领域…...

羊城杯-2023-Crypto

文章目录 Danger_RSA题目描述&#xff1a;题目分析&#xff1a; Easy_3L题目描述&#xff1a;题目分析&#xff1a; XOR贯穿始终题目描述&#xff1a;题目分析&#xff1a; MCeorpkpleer题目描述&#xff1a;题目分析&#xff1a; SigninCrypto题目描述&#xff1a;题目分析&am…...

RabbitMQ快速上手及讲解

前言&#xff1a;在介绍RabbitMQ之前&#xff0c;我们先来看下面一个场景&#xff1a; 1.1.1.1 异步处理 场景说明&#xff1a; 用户注册后&#xff0c;需要发注册邮件和注册短信&#xff0c;传统的做法有两种 1.串行的方式 (1)串行方式&#xff1a;将注册信息写入数据库后&a…...

使用多线程std::thread发挥多核计算优势(解答)

使用多线程std::thread发挥多核计算优势&#xff08;题目&#xff09; 单核无能为力 如果我们的电脑只有一个核&#xff0c;那么我们没有什么更好的办法可以让我们的程序更快。 因为这个作业限制了你修改算法函数。你唯一能做的就是利用你电脑的多核。 使用多线程 由于我们…...

MySQL分页查询详解:优化大数据集的LIMIT和OFFSET

最近在工作中&#xff0c;我们遇到了一个需求&#xff0c;甲方要求直接从数据库导出一个业务模块中所有使用中的工单信息。为了实现这一目标&#xff0c;我编写了一条SQL查询语句&#xff0c;并请求DBA协助导出数据。尽管工单数量并不多&#xff0c;只有3000多条&#xff0c;但…...

解构赋值、函数默认值

暂时性死区&#xff0c;TDZ&#xff08;Temporal Dead Zone&#xff09; var x 1 {let x x//此处声明了x&#xff0c;但是没有对x赋值&#xff0c;相当于在赋值之前引用x&#xff0c;所以会造成报错console.log(x)//报错x is not defined&#xff0c;暂时性死区&#xff0c;…...

【已解决】Mybatis 实现 Group By 动态分组查询

&#x1f389;工作中遇到这样一个需求场景&#xff1a;实现一个统计查询&#xff0c;要求可以根据用户在前端界面筛选的字段进行动态地分组统计。也就是说&#xff0c;后端在实现分组查询的时候&#xff0c;Group By 的字段是不确定的&#xff0c;可能是一个字段、多个字段或者…...

Android修改默认gradle路径

Android Studio每次新建项目&#xff0c;都会默认在C盘生成并下载gradle相关文件&#xff0c;由于C盘空间有限&#xff0c;没多久C盘就飘红了&#xff0c;于是就需要把gradle相关文件转移到其他盘 1、到C盘找到gradle文件 具体路径一般是&#xff1a;C:\Users\用户\ .gradle …...

原生JS+canvas实现炫酷背景

原生JScanvas实现炫酷背景 可以在需要的背景页使用 <!doctype html> <html> <head> <meta charset"utf-8"> <title>HTML5 Canvas矩阵粒子波浪背景动画特效</title> <style> html,body { height:100%; } body { …...

Linux学习之NAS服务器搭建

NAS是Network Attached Storage的缩写&#xff0c;也就是网络附属存储。可以使用自己已经不怎么使用的笔记本搭建一台NAS服务器。 fdisk -l可以看一下各个磁盘的状态。 可以看到有sda、sdb、sdc和sdd等四块硬盘。 lvs、vgs和pvs结合起来看&#xff0c;sdb和sdc没有被使用。 …...

分享码云上8个宝藏又有价值的开源图片编辑器

如果你需要高效地处理图片&#xff0c;那么这8款实用工具是可以尝试的&#xff01; 它们能够进行一键抠图、放大、拼接、转矢量图、图标自动生成以及等操作&#xff0c;让你的工作效率飞升&#xff01; 在Gitee这个最有价值的开源项目计划是Gitee综合评定出的优秀开源项目的展示…...

TCP Header都有啥?

分析&回答 源端口号&#xff08;Source Port&#xff09; &#xff1a;16位&#xff0c;标识主机上发起传送的应用程序&#xff1b; 目的端口&#xff08;Destonation Port&#xff09; &#xff1a;16位&#xff0c;标识主机上传送要到达的应用程序。 源端&#xff0c;目…...

无涯教程-Android - AutoCompleteTextView函数

AutoCompleteTextView是一个类似于EditText的视图&#xff0c;只是它在用户键入时自动显示补充数据。 AutoCompleteTextView - 属性 以下是与AutoCompleteTextView控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。…...

【Docker】 07-安装ElasticSearch、Kibana

安装ElasticSearch 1、拉取镜像 docker pull elasticsearch:6.4.2 2、运行 docker run -p 9200:9200 -p 9300:9300 --name es -d elasticsearch:6.4.2 启动会报错&#xff0c;按照下面流程修改 3、在宿主机中&#xff0c;修改配置sysctl.conf vim /etc/sysctl.conf 加入如下配…...

【数据结构篇】线性表1 --- 顺序表、链表 (万字详解!!)

前言&#xff1a;这篇博客我们重点讲 线性表中的顺序表、链表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列... 线性表在逻辑上是…...

C语言每日一练--Day(17)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;数对 截取字符串 &#x1f493;博主csdn个人主页&#xff1a;小小unico…...

8月琐碎但值得的事情

8月份结束了&#xff0c;最近心态比较好&#xff0c;慢点就慢点&#xff0c;没有那么着急了&#xff0c;可能是因为着急也没啥办法&#xff0c; 8月是比较开心的一个月&#xff0c;可能是做的事情更有盼头了&#xff0c;可能是看了喜欢的书&#xff0c;可能是我变瘦了&#xff…...

苹果Mac系统如何优化流畅的运行?提高运行速度

Mac系统的稳定性和流畅性一直备受大家称赞&#xff0c;这也是大多数人选择Mac的原因&#xff0c;尽管如此&#xff0c;我们仍不时地对Mac进行优化、调整&#xff0c;以使其比以前更快、更流畅地运行。以下是小编分享给各位的Mac优化方法&#xff0c;记得保存哦~ 一、释放被过度…...

Python 类和对象

类的创建 Python语言中&#xff0c;使用class关键字来创建类&#xff0c;其创建方式如下&#xff1a; class ClassName(bases):# class documentation string 类文档字符串&#xff0c;对类进行解释说明class_suiteclass是关键字&#xff0c;bases是要继承的父类&#xff0c;…...

VC++使用Microsoft Speech SDK进行文字TTS朗读

Microsoft Speech SDK下载地址 https://www.microsoft.com/en-us/download/details.aspx?id10121 需要msttss22L.exe、SpeechSDK51.exe、SpeechSDK51LangPack.exe三个&#xff0c;下载后全部安装 使用VS2005建立一个win32控制台项目 朗读"hello word"、中文“你好”…...

FFmpeg4.3.1+h264在windows下编译与VS2017项目集成

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…...

mapboxGL3新特性介绍

概述 8月7日&#xff0c;mapboxGL发布了3版本的更新&#xff0c;本文带大家一起来看看mapboxGL3有哪些新的特性。 新特新 如上图所示&#xff0c;是mapboxGL官网关于新版的介绍&#xff0c;大致翻译如下&#xff1a; 增强了web渲染的质量、便捷程度以及开发人员体验&#xff…...

类ChatGPT大模型LLaMA及其微调模型

1.LLaMA LLaMA的模型架构:RMSNorm/SwiGLU/RoPE/Transfor mer/1-1.4T tokens 1.1对transformer子层的输入归一化 对每个transformer子层的输入使用RMSNorm进行归一化&#xff0c;计算如下&#xff1a; 1.2使用SwiGLU替换ReLU 【Relu激活函数】Relu(x) max(0,x) 。 【GLU激…...