【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.pswp.cn/news/41158.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41158.shtml
英文地址,請注明出處:http://en.pswp.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…

Node + Express 后臺開發 —— 起步

Node Express 后臺開發 —— 起步 前面陸續學習了一下 node、npm、模塊&#xff0c;也稍嘗試 Express&#xff0c;感覺得換一個思路加快進行。 比如筆者對前端的開發已較熟悉&#xff0c;如果領導給一個內部小網站的需求&#xff0c;難道說你得給我配置一個后端&#xff1f;…

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):"…

六圖備份 大容量圖片

1.1、切割&#xff08;9*16&#xff09; # 參考網址&#xff1a;https://blog.csdn.net/weixin_42182534/article/details/125773141?ops_request_misc&request_id&biz_id102&utm_termpython%E6%88%AA%E5%8F%96%E5%9B%BE%E7%89%87%E7%9A%84%E4%B8%80%E9%83%A8%E5%…

為什么在Spring中使用@Autowired時會提示Field injection is not recommended 而@Resource不會

在使用IDEA進行開發時&#xff0c;在字段上使用Spring的依賴注入注解Autowired后會出現如下警告 Field injection is not recommended (字段注入是不被推薦的) 這個原因具體可以看看&#xff1a; 【注解使用】使用Autowired后提示&#xff1a;Field injection is not recomme…

【抖音小玩法-彈幕游戲】開發者功能測試報告提交模板

背景 字節有明確的要求&#xff0c;準入和準出更加嚴格&#xff0c;要求有明確的測試報告。格式如下&#xff1a; *本文參考字節wiki&#xff1a;開發者功能測試報告提交模板 網絡兼容性 請確認在以下網絡類型驗證過插件功能 WIFI 4G 測試機型 請羅列驗證過的雙端機型 An…

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成功舉辦的…

設計模式——經典單例

0、核心要素 // 構造、析構函數私有化&#xff08;一個進程只允許一個對象存在&#xff09; // 對象私有化、靜態化&#xff08;因為接口靜態函數&#xff09; // 對象調用接口靜態化&#xff08;因為靜態函數脫離了類對象&#xff0c;可以直接調用&#xff09; 一、懶漢 唯…

如何更好的維護自己的電腦?

我的筆記本電腦 我使用的華碩天選3是一款游戲本&#xff0c;搭載了英特爾酷睿i7-12700H處理器&#xff0c;16GB內存&#xff0c;512GB固態硬盤和NVIDIA GeForce RTX 3050顯卡。屏幕尺寸為15.6英寸&#xff0c;分辨率為2560x1440。對于日常使用和工作學習娛樂都能滿足要求。 日常…

基于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;被提議出來&…