Redis学习笔记

目录

一、认识NoSQL及Redis

二、通用命令

三、数据结构-基本类型

1、String类型-字符串类型

2、Hash类型

3、List类型

4、Set类型

[​编辑 5、SortedSet类型](#编辑 5、SortedSet类型)

四、Redis的Java客户端-Jedis

[五、Redis的Java客户端-lettuce(Spring Data Redis包含该客户端)](#五、Redis的Java客户端-lettuce(Spring Data Redis包含该客户端))

六、Redis的Java客户端-Redisson

七、SpringDataRedis

1、gradle引入依赖

2、配置application.yml

3、方式一:使用RedisTemplate工具类,需要自定义RedisTemplate处理序列化

4、方式二:StringRedisTemplate类,存储java对象时,需手动完成对象的序列化和反序列化


一、认识NoSQL及Redis

1、SQL与NoSQL对比

复制代码
SQL:数据结构化并且是关联的,使用SQL查询,事务特性ACID,磁盘存储,垂直扩展
使用场景:数据结构固定,相关业务对数据安全性、一致性要求较高

NoSQL:数据非结构化并且无关联,非SQL查询,事务特性BASE,内存存储,水平扩展
使用场景:数据结构不固定,对一致性、安全性要求不高,对性能要求

2、认识Redis

复制代码
Redis诞生于2009年,全称是Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型NoSQL数据库。

特征:
键值(key-value)型,value支持多种不同数据结构,功能丰富
单线程,每个命令具备原子性
低延迟,速度快(基于内存、IO多路复用、良好的编码)
支持数据持久化
支持主从集群、分片集群
支持多语言客户端

3、key结构

复制代码
Redis的key允许有多个单词形成层级结构,多个单词之间用':'隔开

格式示例:
项目名:业务名:类型:id
二、通用命令
bash 复制代码
# 输入ip端口密码连接redis
redis-cli -h 192.168.1.104 -p 6379 -a 123456

# 输入ip端口连接redis
redis-cli -h 192.168.1.104 -p 6379
# 输入密码进入redis
AUTH 123456

# 切换到数据库1
select 1

# 查询所有key,不建议在生产环境设备上使用
keys *

# 查询所有带a的key,不建议在生产环境设备上使用
key a*

# 设置一个key为name,值为张三的数据 返回OK代表成功
set name '张三'

# 同时设置多个key,返回OK代表成功
mset k1 v1 k2 v2

# 删除一个key为name的数据 返回删除成功的个数 支持多个key,空格隔开
del name
del k1 k2 # 删除多个

# 判断key为k1,k2的数据是否存在,不存在返回0,存在则返回存在个数
exists k1 k2

# 设置k1在500秒后过期 返回"1"代表成功
expire k1 500

# 查看k1的有效时间 返回秒数 -1为永久有效 -2为已过期
ttl k1
三、数据结构-基本类型
1、String类型-字符串类型
bash 复制代码
Value是字符串,根据格式不同分为3类:
string:普通字符串
int:整数类型,可以做自增、自减操作
float:浮点类型,可以做自增、自减操作

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.

示例:
# 添加/修改一个String类型键值对 返回OK
set msg "hello world"

# 设置msg在500秒后过期 返回"1"代表成功
expire msg 500

# 批量添加多个String类型键值对 返回OK
mset num 11 score 93.5

# 根据key获取String类型的value,返回"hello world"
get msg

# 根据多个key获取多个String类型的值 返回 1) "11" 2) "93.5"
mget num score 

# 让一个整型的key自增1 返回"12"
incr num 

# 让一个整型的key自增并指定步长 返回 "14"
incrby num 2 

# 让一个浮点类型的数字自增并指定步长 返回 "95.599999999999994"
incrbyfloat score 2.1

# 添加一个String类型的键值对,前提是这个key不存在,否则不执行
setnx msg "test" # 存在msg,未设置成功,返回"0"
setnx test "test" # 不存在test,设置成功,返回"1"

# 添加一个String类型的键值对,并且指定有效期
setex test 100 "test1" # 重置test键的值并设置100秒后过期
ttl test # 查看test过期秒数
2、Hash类型
bash 复制代码
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便;
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD

示例(key为sample:hs:score):
# 添加/修改hash类型key的field值 返回"1"
hset sample:hs:score "张三" 59

# 设置sample:hs:score在500秒后过期 返回"1"代表成功
expire sample:hs:score 500

# 批量添加多个hash类型key的field的值 返回 "OK"
hmset sample:hs:score "李四" 61 "王五" 60

# 获取一个hash类型key的field的值 返回 "59"
hget sample:hs:score "张三"

# 批量获取多个hash类型key的field的值 返回 1) "61" 2) "60"
hmget sample:hs:score "李四" "王五"

# 获取一个hash类型的key中的所有的field和value
# 返回 1) "张三" 2) "59" 3) "李四" 4) "61" 5) "王五" 6) "60"
hgetAll sample:hs:score

# 获取一个hash类型的key中的所有的field 返回 1) "张三" 2) "李四" 3) "王五"
hkeys sample:hs:score

# 获取一个hash类型的key中的所有的value 返回 1) "59" 2) "61" 3) "60"
hvals sample:hs:score

# 让一个hash类型key的字段值自增并指定步长 返回 "69"
hincrby sample:hs:score "张三" 10

# 添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
hsetnx sample:hs:score "张三" 50 # 存在,未设置成功,返回 "0"
hsetnx sample:hs:score "赵六" 50 # 不存在,设置成功,返回 "1"
3、List类型
bash 复制代码
Redis中的List类型与Java中的LinkedList类似,可以看作是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征:有序、元素可以重复、插入和删除快、查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

示例:
# 向列表左侧插入一个或多个元素,row=1 value=b 最后插入的元素在最前面
lpush sample:comment "a" "b"

# 设置sample:comment在500秒后过期 返回"1"代表成功
expire sample:comment 500

# 移除并返回列表左侧的第一个元素,没有则返回null
lpop sample:comment # 返回"b"

# 向列表右侧插入一个或多个元素,row=1 value=a 最后插入的元素在最后面
rpush sample:comment "a" "b"

# 移除并返回列表右侧的第一个元素,返回"b"
rpop sample:comment

# 返回一段角标范围内的所有元素,不包含起始下标值 返回 1) "b"
lrange sample:comment 1 2 

# 与lpop和rpop类似,只不过在没有元素时等待指定时间,而不是直接返回null
# 有值时返回 1) "sample:comment" 2) "b",无值等待10秒返回空白
blpop sample:comment 10 
brpop sample:comment 10
4、Set类型
bash 复制代码
Reids的Set结构与Java中的HashSet类似,可以看作是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:无序、元素不可重复、查找快、支持交集/并集/差集等功能。

示例:
# 向set中添加一个或多个元素
sadd sample:rank 6 7 8

# 设置sample:rank在500秒后过期 返回"1"代表成功
expire sample:rank 500

# 移除set中的指定元素 返回"1"代表成功
srem sample:rank 7

# 返回set中元素的个数 返回"2"
scard sample:rank

# 判断一个元素是否存在于set中
sismember sample:rank  8 # 存在,返回"1" 
sismember sample:rank  7 # 不存在,返回"0"

# 获取set中的所有元素 返回 1)  "6" 2)  "8"
smembers sample:rank
5、SortedSet类型
bash 复制代码
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加hash表。
特性:可排序、元素不重复、查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。

示例(排名默认升序,降序在命令z后面加rev即可):
# 添加一个或多个元素到sorted set,如果已经存在则更新其score值
# 新增行返回"1" 修改行返回"0"
zadd sample:goldRank 101 "张三" 200 "李四" 150 "王五" 

# 设置sample:rank在500秒后过期 返回"1"代表成功
expire sample:goldRank 500

# 删除sorted set中的一个指定元素 返回"1"代表成功 返回"0"代表不存在
zrem sample:goldRank "王五"

# 获取sorted set中的指定元素的score值 返回"200"
zscore sample:goldRank "李四"

# 获取sorted set中指定元素的排名 张三 201 李四 200
zrank sample:goldRank "张三" # 返回"1"
zrevrank sample:goldRank "张三" # 返回"0"

# 获取sorted set中的元素个数 返回"2"
zcard sample:goldRank

# 统计score值在给定范围内的所有元素的个数 返回"1"
zcount sample:goldRank 200 500

# 让sorted set中的指定元素自增,步长为100 返回"201"
zincrby sample:goldRank 100 "张三" 

# 按照score排序后,获取指定排名范围内的元素,排名从0开始,0为最小值 返回 1) "张三"
zrange sample:goldRank 1 10

# 获取指定排名范围内的元素及score 返回 1) "张三" 2) "201"
zrange sample:goldRank 1 10 withscores

# 按照score排序后,获取指定score范围内的元素
zrangebyscore sample:goldRank (200 (500 # 200<score<500 返回 1) "张三"
zrangebyscore sample:goldRank (200 500 # 200<score<=500 返回 1) "张三"
zrangebyscore sample:goldRank 200 500 # 200<=score<=500 返回 1) "李四" 2) "张三"
zrangebyscore sample:goldRank -inf +inf # 显示所有数据 返回 1) "李四" 2) "张三"

# score<=200,并返回score 返回 1) "李四"2) "200"
zrangebyscore sample:goldRank -inf 200 withscores

# 求差集、交集、并集
zdiff zinter zunion
四、Redis的Java客户端-Jedis
bash 复制代码
以Reids命令作为方法名称,学习成本低,简单实用,
但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。
Spring Data Redis包含该客户端

1、推荐使用Jedis连接池代替Jedis的直连方式

java 复制代码
public class JedisConnectionFactory {  
	private static final JedisPool jedisPool;  
	  
	static {  
	JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();  
		//最大连接  
		jedisPoolConfig.setMaxTotal(8);  
		//最大空闲连接  
		jedisPoolConfig.setMaxIdle(8);  
		//最小空闲连接  
		jedisPoolConfig.setMinIdle(0);  
		//设置最长等待时间 ms
		jedisPoolConfig.setMaxWaitMillis(200);  
		//注意没密码不要设置为"",会auth报错
		jedisPool = new JedisPool(jedisPoolConfig, "192.168.10.104", 6379, 1000);  
	}  
	  
	/**  
	* 获取Jedis对象  
	*  
	* @return  
	*/  
	public static Jedis getJedis() {  
		return jedisPool.getResource();  
	}  
}

2、gradle引入依赖

Groovy 复制代码
implementation 'redis.clients:jedis:5.1.2'
implementation 'org.apache.commons:commons-pool2:2.10.0'

3、示例junit测试代码

java 复制代码
@SpringBootTest  
public class JedisApplicationTests {  
	private Jedis jedis;  
	  
	@BeforeEach  
	void setUp() {  
		//jedis直连  
		//jedis = new Jedis("192.168.10.104", 6379);  
		//有密码需设置  
		//jedis.auth("");  
		  
		//使用jedis连接池  
		jedis = JedisConnectionFactory.getJedis();  
		jedis.select(1);
	}  
	  
	@Test  
	void testString() {  
		//设置STRING类型数据  
		String result = jedis.set("name1", "张三");  
		System.out.println("result=" + result);  
		  
		//设置500秒过期  
		jedis.expire("name1", 500);  
		  
		//根据key获取value  
		String name = jedis.get("name1");  
		System.out.println("name=" + name);  
	}  
	  
	@Test  
	void testHash() {  
		//创建hash并添加键值对  
		jedis.hset("sample:hs:score", "张三", "100");  
		  
		//设置500秒过期  
		jedis.expire("sample:hs:score", 500);  
		  
		//追加键值对到hash  
		HashMap<String, String> map = new HashMap<>();  
		map.put("李四", "200");  
		map.put("王五", "150");  
		jedis.hmset("sample:hs:score", map);  
		  
		//获取hash所有键值对  
		Map<String, String> m = jedis.hgetAll("sample:hs:score");  
		System.out.println(m.toString());  
	}  
	  
	@Test  
	void testList() {  
		//创建list并插入数据  
		jedis.lpush("sample:comment", "a", "b");  
		jedis.rpush("sample:comment", "c", "d");  
		List<String> list = jedis.lrange("sample:comment", 0, 100);  
		list.forEach(System.out::println);  
		  
		//设置500秒过期  
		jedis.expire("sample:comment", 500);  
		  
		//从左侧移除数据  
		jedis.lpop("sample:comment");  
	}  
	  
	@Test  
	void testSet() {  
		//创建set并插入数据  
		jedis.sadd("sample:rank", "6", "7", "8");  
		  
		//设置500秒过期  
		jedis.expire("sample:rank", 500);  
		  
		//移除某个元素  
		jedis.srem("sample:rank", "6");  
		  
		//获取所有元素  
		Set<String> set = jedis.smembers("sample:rank");  
		System.out.println("数据:" + set.toString());  
	}  
	  
	@Test  
	void testSortedSet() {  
		jedis.zadd("sample:goldRank", 100, "张三");  
		HashMap<String, Double> map = new HashMap<>();  
		map.put("李四", 200D);  
		map.put("王五", 150D);  
		  
		//设置500秒过期  
		jedis.expire("sample:goldRank", 500);  
		jedis.zrem("sample:goldRank", "王五");  
		  
		//获取指定排名范围内的元素及score  
		List<Tuple> list = jedis.zrangeWithScores("sample:goldRank", 1, 10);  
		list.forEach(System.out::println);  
	}  
	  
	@AfterEach  
	void tearDown() {  
		//释放资源  
		if (jedis != null) {  
		jedis.close();  
		}  
	}  
}
五、Redis的Java客户端-lettuce(Spring Data Redis包含该客户端)
java 复制代码
lettuce是基于netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。
支持Redis的哨兵模式、集群模式和管道模式。
六、Redis的Java客户端-Redisson
java 复制代码
Redisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。
包含了诸如Map、Queue、Lock、semaphore、AtomicLong等强大功能。
七、SpringDataRedis
java 复制代码
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,
其中对Redis的集成模块就叫做SpringDataRedis,
官网地址:https://spring.io/projects/spring-data-redis

提供了对不同Redis客户端的整合(Lettuce和Jedis)
提供了RedisTemplate统一API来操作Redis
支持Redis的发布订阅模型
支持Redis哨兵和Redis集群
支持基于Lettuce的响应式编程
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
支持基于Redis的JDK Collection实现
1、gradle引入依赖
Groovy 复制代码
//spring-boot-starter-data-redis依赖  
implementation 'org.springframework.boot:spring-boot-starter-data-redis'  
  
//连接池依赖,jedis和spring-boot-starter-data-redis都需要  
implementation 'org.apache.commons:commons-pool2:2.10.0'
2、配置application.yml
Groovy 复制代码
server:  
	port: 8050  
  
spring:  
	redis:  
		host: localhost  
		port: 6379  
		password:  
		lettuce: # jedis:  
			pool:  
				max-active: 8 # 最大连接  
				max-idle: 8 # 最大空闲连接  
				min-idle: 0 # 最小空闲连接  
				max-wait: 100 # 连接等待时间  
		database: 1
3、方式一:使用RedisTemplate工具类,需要自定义RedisTemplate处理序列化
java 复制代码
/**
* 自定义RedisTemplate,修改RedisTemplate的序列化器,避免key-value乱码,
* 但java对象json序列化会将类的class类型写入json结果,存入Redis,会带来额外的内存开销。
*/
@Configuration  
public class RedisConfig {  
	@Bean  
	public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){  
		//创建RedisTemplate对象  
		RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();  
		  
		//设置连接工厂  
		redisTemplate.setConnectionFactory(connectionFactory);  
		  
		//创建Json序列化工具  
		GenericJackson2JsonRedisSerializer jsonRedisSerializer=new GenericJackson2JsonRedisSerializer();  
		  
		//设置key的序列化  
		redisTemplate.setKeySerializer(RedisSerializer.string());  
		redisTemplate.setHashKeySerializer(RedisSerializer.string());  
		  
		//设置value的序列化  
		redisTemplate.setValueSerializer(jsonRedisSerializer);  
		redisTemplate.setHashValueSerializer(jsonRedisSerializer);  
		  
		//返回  
		return redisTemplate;  
	}  
}
java 复制代码
// String类型存实体数据格式
{
	"@class": "com.fafa.jedis.User",
	"userId": 10001,
	"name": "张三三"
}
java 复制代码
/**
* RedisTemplate测试示例
*/
@SpringBootTest  
public class RedisTemplateTests {  
	@Autowired  
	private RedisTemplate<String, Object> redisTemplate;  
	  
	@Test  
	void testString() {  
		//插入一条String类型数据  
		ValueOperations valueOperations = redisTemplate.opsForValue();  
		valueOperations.set("name", "李四");  
		  
		//存入java对象,会将类的class类型写入json  
		User user = new User(10001, "张三三");  
		valueOperations.set("user", user);  
		  
		//设置500秒过期  
		valueOperations.getOperations().expire("name", 500, TimeUnit.SECONDS);  
		  
		//读取一条String类型数据  
		Object name = valueOperations.get("name");  
		System.out.println("数据name=" + name);  
	}  
	  
	@Test  
	void testHash() {  
		//插入一条Hash类型数据  
		HashOperations hashOperations = redisTemplate.opsForHash();  
		hashOperations.put("sample:hs:score", "张三", "100");  
		  
		//追加键值对到hash  
		HashMap<String, String> map = new HashMap<>();  
		map.put("李四", "200");  
		map.put("王五", "150");  
		hashOperations.putAll("sample:hs:score", map);  
		  
		//设置500秒过期  
		hashOperations.getOperations().expire("sample:hs:score", 500, TimeUnit.SECONDS);  
		  
		//获取hash所有键值对  
		Map<String, String> m = hashOperations.entries("sample:hs:score");  
		System.out.println("数据:" + m.toString());  
	}  
	  
	@Test  
	void testList() {  
		//创建list并插入数据  
		ListOperations listOperations = redisTemplate.opsForList();  
		listOperations.leftPushAll("sample:comment", "a", "b");  
		listOperations.rightPushAll("sample:comment", "c", "d");  
		List<String> list = listOperations.range("sample:comment", 0, 100);  
		list.forEach(System.out::println);  
		  
		//设置500秒过期  
		listOperations.getOperations().expire("sample:comment", 500, TimeUnit.SECONDS);  
		  
		//从左侧移除数据  
		listOperations.leftPop("sample:comment");  
	}  
	  
	@Test  
	void testSet() {  
		//创建set并插入数据  
		SetOperations setOperations = redisTemplate.opsForSet();  
		setOperations.add("sample:rank", "6", "7", "8");  
		  
		//设置500秒过期  
		setOperations.getOperations().expire("sample:rank", 500, TimeUnit.SECONDS);  
		  
		//移除某个元素  
		setOperations.remove("sample:rank", "6");  
		  
		//获取所有元素  
		Set<String> set = setOperations.members("sample:rank");  
		System.out.println("数据:" + set.toString());  
	}  
	  
	@Test  
	void testSortedSet() {  
		ZSetOperations sortedSetOption = redisTemplate.opsForZSet();  
		sortedSetOption.add("sample:goldRank", "张三", 100);  
		HashMap<String, Double> map = new HashMap<>();  
		map.put("李四", 200D);  
		map.put("王五", 150D);  
		  
		//设置500秒过期  
		sortedSetOption.getOperations().expire("sample:goldRank", 500, TimeUnit.SECONDS);  
		sortedSetOption.remove("sample:goldRank", "王五");  
		  
		//获取指定排名范围内的元素  
		Set list = sortedSetOption.range("sample:goldRank", 0, 10);  
		list.forEach(System.out::println);  
	}  
}
4、方式二:StringRedisTemplate类,存储java对象时,需手动完成对象的序列化和反序列化
java 复制代码
@SpringBootTest  
public class StringRedisTemplateTests {  
	@Autowired  
	private StringRedisTemplate stringRedisTemplate;  
	  
	//JSON工具  
	private static final ObjectMapper mapper = new ObjectMapper();  
	  
	@Test  
	void testString() throws JsonProcessingException {  
		//插入一条String类型数据  
		ValueOperations valueOperations = stringRedisTemplate.opsForValue();  
		valueOperations.set("name", "李四");  
		  
		//读取一条String类型数据  
		Object name = valueOperations.get("name");  
		System.out.println("数据name=" + name);  
		  
		//创建对象  
		User user = new User(10001, "张三三");  
		  
		//jackson手动序列化  
		String json = mapper.writeValueAsString(user);  
		//写入数据到redis  
		valueOperations.set("user", json);  
		  
		//读取数据  
		String val = (String) valueOperations.get("user");  
		//jackson反序列化  
		User user1 = mapper.readValue(val, User.class);  
		System.out.println("user1=" + user1);  
		  
		//设置500秒过期  
		valueOperations.getOperations().expire("name", 500, TimeUnit.SECONDS);  
	}  
	  
	@Test  
	void testHash() {  
		//插入一条Hash类型数据  
		HashOperations hashOperations = stringRedisTemplate.opsForHash();  
		hashOperations.put("sample:hs:score", "张三", "100");  
		  
		//追加键值对到hash  
		HashMap<String, String> map = new HashMap<>();  
		map.put("李四", "200");  
		map.put("王五", "150");  
		hashOperations.putAll("sample:hs:score", map);  
		  
		//设置500秒过期  
		hashOperations.getOperations().expire("sample:hs:score", 500, TimeUnit.SECONDS);  
		  
		//获取hash所有键值对  
		Map<String, String> m = hashOperations.entries("sample:hs:score");  
		System.out.println("数据:" + m.toString());  
	}  
	  
	@Test  
	void testList() {  
		//创建list并插入数据  
		ListOperations listOperations = stringRedisTemplate.opsForList();  
		listOperations.leftPushAll("sample:comment", "a", "b");  
		listOperations.rightPushAll("sample:comment", "c", "d");  
		List<String> list = listOperations.range("sample:comment", 0, 100);  
		list.forEach(System.out::println);  
		  
		//设置500秒过期  
		listOperations.getOperations().expire("sample:comment", 500, TimeUnit.SECONDS);  
		  
		//从左侧移除数据  
		listOperations.leftPop("sample:comment");  
	}  
	  
	@Test  
	void testSet() {  
		//创建set并插入数据  
		SetOperations setOperations = stringRedisTemplate.opsForSet();  
		setOperations.add("sample:rank", "6", "7", "8");  
		  
		//设置500秒过期  
		setOperations.getOperations().expire("sample:rank", 500, TimeUnit.SECONDS);  
		  
		//移除某个元素  
		setOperations.remove("sample:rank", "6");  
		  
		//获取所有元素  
		Set<String> set = setOperations.members("sample:rank");  
		System.out.println("数据:" + set.toString());  
	}  
	  
	@Test  
	void testSortedSet() {  
		ZSetOperations sortedSetOption = stringRedisTemplate.opsForZSet();  
		sortedSetOption.add("sample:goldRank", "张三", 100);  
		HashMap<String, Double> map = new HashMap<>();  
		map.put("李四", 200D);  
		map.put("王五", 150D);  
		  
		//设置500秒过期  
		sortedSetOption.getOperations().expire("sample:goldRank", 500, TimeUnit.SECONDS);  
		sortedSetOption.remove("sample:goldRank", "王五");  
		  
		//获取指定排名范围内的元素  
		Set list = sortedSetOption.range("sample:goldRank", 0, 10);  
		list.forEach(System.out::println);  
	}  
}
相关推荐
派大鑫wink14 分钟前
纯前端实现软考中级·软件设计师备考学习网站
前端·学习
smartpi_ai40 分钟前
51 单片机写好了,大模型语音怎么接?17T 全驱动的现成方案、双板串口分工的边界与画板前的三项硬件确认
单片机·嵌入式硬件·学习
wujiuhsu1 小时前
图片里的简体中文怎么翻译成繁体?商品图、详情页和海报批量转换方法
经验分享·笔记
wefg11 小时前
【Redis】安装与启动
数据库·redis·缓存
大圣编蚕2 小时前
常见的 Redis 面试题(高频整理)
数据库·redis·缓存
辣知2 小时前
辣知·化智82 人类文明演化规律文化传承
学习
小猿姐2 小时前
KubeBlocks Enterprise v2.3 发布!
redis·kubernetes·aiops
梅菲塔亚3 小时前
博客_Redis入门到实战
数据库·redis·缓存
蓝胖的四次元口袋3 小时前
Redis 知识梳理(3)
数据库·redis·缓存
小雪崩4 小时前
嵌入式学习:Linux驱动开发总结
linux·驱动开发·学习