【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;}}}

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

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

相关文章

Vector

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析2 目录 &#x1f449;&#x1f3fb;vector概念&#x1f449;&#x1f3fb;vector constr…

selenium 选定ul-li下拉选项中某个指定选项

场景&#xff1a;selenium的下拉选项是ul-li模式&#xff0c;选定某个指定的选项。 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 显示等待def select_li(self, text, *ul_locator):"…

python+django+mysql高校校园外卖点餐系统--计算机毕设项目

本文的研究目标是以高校校园外卖点餐为对象&#xff0c;使其高校校园外卖点餐为目标&#xff0c;使得高校校园外卖点餐的信息化体系发展水平提高。论文的研究内容包括对个人中心、美食分类管理、用户管理、商家管理、美食信息管理、工作人员管理、安全检查管理、系统管理、订单…

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 1.Matlab实现GRU门控循环单元时间序列预测未…

复数混频器、零中频架构和高级算法开发

文章里讲解了关于射频IQ调制器、零中频架构相关的原理及技术&#xff0c;全都是干货&#xff01;其实好多同行对软件无线电的原理、IQ调制、镜像抑制都是一知半解&#xff0c;知其然不知其所以然。好好研读这篇文章&#xff0c;相信会让你有种恍然大悟的感觉。 RF工程常被视为…

Shell学习笔记之基础部分

Shell基础&#xff1a; 查看操作系统支持的shell&#xff1a; [rootrhel9 ansible]# cat /etc/shells /bin/sh /bin/bash /usr/bin/sh /usr/bin/bashShell的基本元素&#xff1a; 声明&#xff1a;声明用哪个命令解释器来解释并执行当前脚本文件中的语句&#xff0c;一般写的…

大语言模型与语义搜索;钉钉个人版启动内测,提供多项AI服务

&#x1f989; AI新闻 &#x1f680; 钉钉个人版启动内测&#xff0c;提供多项AI服务 摘要&#xff1a;钉钉个人版正式开始内测&#xff0c;面向小团队、个人用户、高校大学生等人群。该版本具有AI为核心的功能&#xff0c;包括文生文AI、文生图AI和角色化对话等。用户可通过…

【IEEE会议】第二届IEEE云计算、大数据应用与软件工程国际学术会议 (CBASE2023)

第二届IEEE云计算、大数据应用与软件工程国际学术会议 (CBASE2023&#xff09; 随着大数据时代的到来&#xff0c;对数据获取的随时性和对计算的需求也在逐渐增长。为推动大数据时代的云计算与软件工程的发展&#xff0c;促进该领域学术交流&#xff0c;在CBASE 2022成功举办的…

基于docker搭建pytest自动化测试环境(docker+pytest+jenkins+allure)

pytest搭建自动化测试环境&#xff08;dockerpytestjenkinsallure&#xff09; 这里我以ubuntu18为例 如果有docker环境&#xff0c;可以直接拉取我打包好的镜像docker pull ziyigun/jenkins:v1.0 1 搭建Docker 1.1 安装docker # 配置docker安装环境 sudo apt-get install ap…

润和软件HopeStage操作系统正式上架阿里云、华为云、腾讯云商店

近日&#xff0c;润和软件HopeStage操作系统正式上架阿里云、华为云、腾讯云商店。 随着科技的发展&#xff0c;云服务成为现代社会信息和资讯的交换、共享、存储、检索、应用等重要方式。阿里云、华为云、腾讯云作为我国云服务市场三巨头&#xff0c;其云商店产品全面覆盖云、…

Nvidia Jetson 编解码开发(1)介绍

前言 由于项目需要,需要开发Jetson平台的硬件编解码; 优化CPU带宽,后续主要以介绍硬件编解码为主 1.Jetson各平台编解码性能说明 如下是拿了Jetson nano/tx2/Xavier等几个平台做对比; 这里说明的编解码性能主要是对硬件来说的 2. 编解码实现说明 2.1 软件编解码 优点:…

Idea中隐藏指定文件或指定类型文件

Setting ->Editor ->Code Style->File Types → Ignored Files and Folders输入要隐藏的文件名&#xff0c;支持*号通配符回车确认添加

Windows权限维持—自启动映像劫持粘滞键辅助屏保后门WinLogon

Windows权限维持—自启动&映像劫持&粘滞键&辅助屏保后门&WinLogon 1. 前置2. 自启动2.1. 路径加载2.1.1. 放置文件2.1.2. 重启主机 2.2. 服务加载2.2.1. 创建服务2.2.2. 查看服务2.2.3. 重启主机 2.3. 注册表加载2.3.1. 添加启动项2.3.2. 查看注册表2.3.3. 重启…

YOLOv5基础知识入门(7)— NMS(非极大值抑制)原理解析

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。NMS是指非极大值抑制&#xff08;non maximum suppression&#xff09;&#xff0c;它是一种常用于物体检测任务的算法。在物体检测中&#xff0c;通常会有多个预测框&#xff08;bounding box&#xff09;被提议出来&…

机器学习深度学习——transformer(机器翻译的再实现)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——自注意力和位置编码&#xff08;数学推导代码实现&#xff09; &#x1f4da;订阅专栏&#xff1a;机器…

【论文阅读】 Model Sparsity Can Simplify Machine Unlearning

Model Sparsity Can Simplify Machine Unlearning 背景主要内容Contribution Ⅰ&#xff1a;对Machine Unlearning的一个全面的理解Contribution Ⅱ&#xff1a;说明model sparsity对Machine Unlearning的好处Pruning方法的选择sparse-aware的unlearning framework Experiments…

JetBrains IDE远程开发功能可供GitHub用户使用

JetBrains与GitHub去年已达成合作&#xff0c;提供GitHub Codespaces 与 JetBrains Gateway 之间的集成。 GitHub Codespaces允许用户创建安全、可配置、专属的云端开发环境&#xff0c;此集成意味着您可以通过JetBrains Gateway使用在 GitHub Codespaces 中运行喜欢的IDE进行…

VScode搭建Opencv(C++开发环境)

VScode配置Opencv 一、 软件版本二 、下载软件2.1 MinGw下载2.2 Cmake下载2.3 Opencv下载 三、编译3.1 cmake-gui3.2 make3.3 install 四、 VScode配置4.1 launch.json4.2 c_cpp_properties.json4.3 tasks.json 五、测试 一、 软件版本 cmake :cmake-3.27.2-windows-x86_64 Mi…

JAVA基础知识(一)——Java语言描述、变量和运算符

TOC(Java语言描述、变量和运算符) 一、JAVA语言描述 1.1 java语言描述 JDK、JRE、jVM三者之间的关系&#xff0c;以及JDK、JRE包含的主要结构有哪些&#xff1f; JDKJre java的开发工具&#xff08;javac.exe java.exe javadoc.exe&#xff09; jre jvmjava的核心类库 为什…

【JavaEE基础学习打卡03】Java EE 平台有哪些内容?

目录 前言一、Java EE平台说明二、Java EE平台容器及组件1.平台容器2.平台组件 三、JavaEE平台API服务1.API服务概览2.平台API 总结 前言 &#x1f4dc; 本系列教程适用于Java Web初学者、爱好者&#xff0c;小白白。我们的天赋并不高&#xff0c;可贵在努力&#xff0c;坚持不…