Spring Boot 3.3 【九】Redis 的五种数据结构深入浅出(String & List & Set & Hash & Zset)
如果觉得本文能够帮到您,请关注🌟、点赞👍、收藏📚,让这份美好延续下去!
一、Redis 数据结构简介
在现代应用开发中,高效的数据存储和管理是构建强大系统的关键。Redis 作为一种高性能的内存数据库,以其丰富的数据结构和快速的操作能力而备受青睐。Spring Boot 3 作为流行的开发框架,为整合 Redis 提供了便捷的方式。
在本文中,我们将深入探讨 Spring Boot 3 如何与 Redis 进行整合,并详细介绍对 Redis 的五种主要数据结构 —— 字符串(String)、列表(List)、集合(Set)、哈希(Hash)和有序集合(Sorted Set)的操作。通过掌握这些操作,我们能够充分发挥 Redis 的优势,提升应用的性能和灵活性。
二、深入 Redis 的五种数据结构
(一)字符串
1. Redis String 简介
Redis 的字符串(String)是最基本的数据类型,由一个键和一个值组成,其中键和值都可以是字符串。其值的最大限制为 512MB,这使得它能够存储相对较大的数据量。String 类型是 Redis 中最为常用的数据类型之一,因为它支持多种操作,非常灵活。
它支持简单的 GET 和 SET 操作,可以快速地获取和设置字符串的值。此外,还支持自增(INCR)和自减(DECR)操作,这使得它非常适合用于实现 计数器功能,比如 统计网站的访问量、点赞数等。同时,还可以进行字符串拼接等操作,进一步扩展了其应用场景。
典型的应用场景 包括缓存数据,比如 存储用户登录状态、Token 以及各种 配置信息等。在这些场景中,String 类型可以快速地存储和读取数据,提高系统的响应速度。另外,结合 SETNX 命令,还可以用字符串来实现简单的 分布式锁,保证在分布式环境下对共享资源的互斥访问。
底层原理方面,Redis 底层对字符串使用的是简单动态字符串(SDS)。SDS 不仅仅是对 C 字符串的简单封装,它还加入了长度属性,使得获取字符串长度的操作时间复杂度为 O (1)。同时,SDS 还采用了空间预留策略,当进行字符串拼接等操作时,可以减少内存分配的次数,提高性能。此外,SDS 支持二进制安全,可以存储文本和二进制数据,这使得它在存储各种类型的数据时更加灵活。
2. redisTemplate.opsForValue().set() 方法
public void setString(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
这个方法用于将一个字符串值存储到 Redis 中。通过 redisTemplate.opsForValue() 获取到针对字符串类型的操作对象,然后调用 set 方法,传入键和值,将指定的值存储到 Redis 中,与该键对应。如果键已经存在,会覆盖原有值。
3. redisTemplate.opsForValue().get() 方法
public String getString(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
此方法用于从 Redis 中获取与给定键对应的值。同样通过 redisTemplate.opsForValue() 获取操作对象,然后调用 get 方法传入键,返回存储在 Redis 中的字符串值。如果键不存在,将返回 null。
(二)列表
1. Redis List 简介
Redis 的列表(List)是一个 双向链表,可以从头部或尾部插入、删除元素。常用的命令包括 LPUSH(从头部插入元素)、RPUSH(从尾部插入元素)、LPOP(从头部弹出元素)、RPOP(从尾部弹出元素)等。
Redis 的列表还支持阻塞操作,如 BLPOP 和 BRPOP。当列表为空时,这些命令可以阻塞等待,直到有元素被插入到列表中。这种阻塞操作使得列表非常适合用于实现 消息队列 等场景。
典型的应用场景之一是作为消息队列。可以使用 LPUSH 将消息放入队列的头部,使用 RPOP 或 BRPOP 从队列的尾部弹出消息进行处理。这种方式实现的消息队列简单高效,适用于各种异步处理场景。另一个应用场景是 任务调度,在异步任务分发系统中,可以将任务放入列表中,由多个消费者去消费任务,实现任务的并行处理。
列表采用 双向链表(quicklist) 实现。对于较短的列表,Redis 会使用 压缩列表(ziplist) 来节省内存。压缩列表是一种紧凑的 内存数据结构,可以在一定程度上减少内存占用。但是,当列表长度增加时,为了保证操作的时间复杂度,Redis 会自动将其转换为真正的双向链表。这样可以在不同的场景下平衡内存使用和操作效率。
2. redisTemplate.opsForList().leftPush 方法
(1)以下方法将一个元素插入到指定列表的左侧。
使用 redisTemplate.opsForList() 获取针对列表类型的操作对象,调用 leftPush 方法,传入键和要插入的元素值。随着元素不断插入,列表的头部不断变化,最先插入的元素会逐渐移向列表的尾部。
public void leftPush(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
(2)依次向 list 中存入元素 aaa,bbb,ccc,aaa,ddd:
@PostMapping("/leftPush")
public void leftPush() {
String aaa = "aaa", bbb="bbb", ccc="ccc", ddd="ddd";
userService.leftPush(aaa);
userService.leftPush(bbb);
userService.leftPush(ccc);
userService.leftPush(aaa);
userService.leftPush(ddd);
}
(3)可以看到 redis 中,先存储的在右侧(底部),后存储的在左侧(上部):
3. redisTemplate.opsForList().rightPush 方法
(1)该方法是从列表 右侧 插入元素:
public void rightPush(String key, Object value) {
redisTemplate.opsForList().rightPush(key, value);
}
@PostMapping("/rightPush")
public void rightPush() {
String eee = "eee", fff="fff";
userService.rightPush(eee);
userService.rightPush(fff);
}
(2)查看 redis中 的元素,eee 和 fff 是从列表右侧插入:
4. 从列表左侧和右侧取出元素
(1)可以从列表左侧或者右侧取出(删除)一个元素。
即使用 leftPop 和 rightPop 方法,从列表的左侧或右侧移除并返回一个元素。如果列表为空,将返回 null。
/*
* 从左侧取出一个元素
*/
public Object leftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/*
* 从右侧取出一个元素
*/
public Object rightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
(2)从左侧取出了元素 ddd, 从右侧取出了元素 fff:
5. 获取列表长度
(1)redisTemplate.opsForList().size() 方法:
// 获取列表长度
public Long listLength(String key) {
return redisTemplate.opsForList().size(key);
}
(2)请求结果为 5:
6. 获取列表指定范围的元素
(1)以下方法的作用是获取指定键对应列表中从 start 索引到 end 索引范围内的元素。
它通过调用 redisTemplate.opsForList().range(key, start, end) 实现,可用于获取列表的一部分进行处理或展示,其中列表的第一个元素从 0 开始。
// 获取列表指定范围的元素
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
(2)Redis 列表的原数据如下图所示:
(3)比如获取列表从索引 1(第二个元素) 开始 至 索引 4:
/*
* 获取列表指定范围元素
*/
@GetMapping("range")
public List<Object> range(String key) {
return userService.range(key, 1, 4);
}
得出如下结果:
(4)获取整个列表
输入起始位置为 0,
/*
* 获取列表指定元素
*/
@GetMapping("range")
public List<Object> range(String key) {
return userService.range(key, 0, -1);
}
(三)无序集合
1. Redis Set 简介
Redis 的集合(Set)是一个 无序的、唯一的 元素集合。这意味着集合中的元素是唯一的,不会有重复元素。集合提供了类似于数学集合的操作,支持交集(SINTER)、并集(SUNION)、差集(SDIFF)等。
常见的 Redis 操作 包括 SADD 用于向集合中添加元素、SREM 用于从集合中删除元素、SISMEMBER 用于判断一个元素是否在集合中、SMEMBERS 用于获取集合中的所有元素等。
典型的应用场景 之一是 标签系统。可以将用户标签存储为集合,每个集合代表一个用户群体。通过集合的交集、并集和差集等操作,可以方便地找出同时拥有某几个标签的用户,或者找出具有特定标签组合的用户群体。另一个应用场景是 去重功能。在某些场景下,比如 热门搜索词、访问日志的去重等,可以通过集合的唯一性特性来避免重复数据的存储和处理。
集合在 小集合 时使用 整数集合(intset) 来存储元素。整数集合是一种紧凑的整数存储结构,可以节省内存。当集合中的元素数量增加或者元素类型不是整数时,会自动转换为 哈希表(hashtable) 实现。通过哈希表的快速查找特性,可以实现 O (1) 的时间复杂度来判断元素是否存在于集合中。
2. 代码示例
Redis Util 工具类的各个方法对应 Redis 集合的不同操作:
package com.jsglxx.redis;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class CacheUtil {
private final RedisTemplate<String, Object> redisTemplate;
public CacheUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 向集合中添加元素
public void addToSet(String key, Object value) {
redisTemplate.opsForSet().add(key, value);
}
// 从集合中移除元素
public void removeFromSet(String key, Object value) {
redisTemplate.opsForSet().remove(key, value);
}
// 判断元素是否在集合中
public boolean isMemberOfSet(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
// 获取集合中的所有元素
public Set<Object> getSetMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
}
- addToSet 方法使用 redisTemplate.opsForSet().add(key, value) 向指定键对应的集合中添加元素。
- removeFromSet 方法使用 redisTemplate.opsForSet().remove(key, value) 从集合中移除指定元素。
- isMemberOfSet 方法使用 redisTemplate.opsForSet().isMember(key, value) 判断给定元素是否在集合中。
- getSetMembers 方法使用 redisTemplate.opsForSet().members(key) 获取集合中的所有元素。
3. 测试方法:
在以下测试方法中,首先向两个集合中添加元素,然后分别测试各个方法,输出结果以验证方法的正确性。
public void testSet() {
String setKey1 = "set1";
String setKey2 = "set2";
// 向集合 1 添加元素
cacheUtil.addToSet(setKey1, "element1");
cacheUtil.addToSet(setKey1, "element2");
cacheUtil.addToSet(setKey1, "element3");
// 向集合 2 添加元素
cacheUtil.addToSet(setKey2, "element2");
cacheUtil.addToSet(setKey2, "element3");
cacheUtil.addToSet(setKey2, "element4");
// 判断元素是否在集合中
boolean isMember = cacheUtil.isMemberOfSet(setKey1, "element2");
System.out.println("【元素element2】在【集合set1】中吗? " + isMember);
// 获取集合中的所有元素
Set<Object> setMembers1 = cacheUtil.getSetMembers(setKey1);
System.out.println("【集合set1】中的所有元素: " + setMembers1);
Set<Object> setMembers2 = cacheUtil.getSetMembers(setKey2);
System.out.println("【集合set2】中的所有元素: " + setMembers2);
}
4. 结果输出
【元素element2】在【集合set1】中吗? true
【集合set1】中的所有元素: [element1, element2, element3]
【集合set2】中的所有元素: [element2, element3, element4]
(四)Hash
1. Redis 哈希介绍
Redis 的哈希(Hash)是一个 =键值对集合,非常适合用于存储对象。每个键可以有多个字段,每个字段都对应一个值。这种结构使得可以将一个复杂的对象拆分成多个字段进行存储,方便进行管理和查询。
常用的 Redis 操作包括 HSET 用于设置字段的值、HGET 用于获取字段的值、HDEL 用于删除特定字段等。这些操作使得对哈希的管理非常方便,可以根据需要快速地添加、修改和删除字段。
典型的应用场景 之一是 存储用户信息。可以将用户 ID 作为键,用户的各种属性(如姓名、年龄、性别等)作为字段,存储在哈希中。这样可以避免将整个用户对象序列化成字符串进行存储,在查询和更新用户信息时更加高效。另一个应用场景是 配置项管理,将不同的配置项存储在哈希中,方便根据字段名快速访问和更新某个特定的配置。
哈希使用了两种底层数据结构。在 小数据量 时,使用 压缩列表(ziplist) 来存储哈希。压缩列表是一种紧凑的内存数据结构,可以节省内存空间。但是,随着哈希表的增长,当数据量达到一定程度时,会自动转换为 哈希表(hashtable)。哈希表具有更高的查询效率,可以保证在大数据量时仍然能够快速地进行字段的查找和操作。
2. Redis 工具类代码示例
package com.jsglxx.redis;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class CacheUtil {
private final RedisTemplate<String, Object> redisTemplate;
public CacheUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 设置哈希字段的值
public void setHashField(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
// 获取哈希字段的值
public Object getHashField(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
// 删除哈希字段
public void deleteHashField(String key, String... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
// 获取哈希的所有字段名
public Set<Object> getHashKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
// 获取哈希的所有值
public List<Object> getHashValues(String key) {
return redisTemplate.opsForHash().values(key);
}
// 获取哈希的所有字段和值
public Map<Object, Object> getHashAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
}
- setHashField 方法使用 redisTemplate.opsForHash().put(key, field, value) 将指定的值设置到哈希表中给定的字段。
- getHashField 方法使用 redisTemplate.opsForHash().get(key, field) 获取哈希表中指定字段的值。
- deleteHashField 方法使用 redisTemplate.opsForHash().delete(key, fields) 删除哈希表中的一个或多个字段。
- getHashKeys 方法使用 redisTemplate.opsForHash().keys(key) 获取哈希表中的所有字段名。
- getHashValues 方法使用 redisTemplate.opsForHash().values(key) 获取哈希表中的所有值。
- getHashAll 方法使用 redisTemplate.opsForHash().entries(key) 获取哈希表中的所有字段和值,以一个 Map 的形式返回。
3. 测试类代码
在测试类中,首先设置哈希表的字段值,然后分别测试各个方法,输出结果以验证方法的正确性。
public void testHash() {
String hashKey = "myHash";
// 设置哈希字段的值
cacheUtil.setHashField(hashKey, "field1", "value1");
cacheUtil.setHashField(hashKey, "field2", "value2");
cacheUtil.setHashField(hashKey, "field3", "value3");
// 获取哈希字段的值
Object value = cacheUtil.getHashField(hashKey, "field2");
System.out.println("field2 的值为: " + value);
// 删除哈希字段
cacheUtil.deleteHashField(hashKey, "field1");
// 获取哈希的所有字段名
Set<Object> keys = cacheUtil.getHashKeys(hashKey);
System.out.println("Hash表中的所有键: " + keys);
// 获取哈希的所有值
List<Object> values = cacheUtil.getHashValues(hashKey);
System.out.println("Hash表中的所有值: " + values);
// 获取哈希的所有字段和值
Map<Object, Object> allEntries = cacheUtil.getHashAll(hashKey);
System.out.println("hash表中的所有键值对: " + allEntries);
}
4. 输出结果
field2 的值为: value2
Hash表中的所有键: [field2, field3]
Hash表中的所有值: [value2, value3]
hash表中的所有键值对: {field2=value2, field3=value3}
(五)有序集合
1. Redis Zset 简介
Redis 的有序集合是一种特殊的集合,其中 每个元素都关联一个分数。集合中的元素会按照 分数排序。支持的操作包括 ZADD 用于向有序集合中添加元素并指定分数、ZRANGE 和 ZREVRANGE 分别用于按照分数从小到大和从大到小获取元素、ZCOUNT 用于统计分数范围内的元素数量等。
典型的应用场景之一是 排行榜。比如在游戏中,可以将玩家的分数作为有序集合的元素分数,玩家 ID 作为元素,通过 ZADD 添加玩家及其分数。然后可以使用 ZRANGE 或 ZREVRANGE 获取排名靠前的玩家。另一个应用场景是 延迟任务。可以通过设置元素的分数为任务执行的时间,将任务存储在有序集合中。然后按照时间从集合中取出需要执行的任务进行处理。
有序集合底层使用的是 跳表(Skiplist) 和 哈希表 相结合的数据结构。跳表是一种高效的有序数据结构,它使有序集合支持快速的范围查询和插入操作,时间复杂度为 O (log n)。而哈希表则保证了元素的快速定位,使得可以在 O (1) 的时间复杂度内判断元素是否存在于有序集合中。这种结合的方式既保证了查询效率,又能够有效地管理元素的顺序。
2. Redis 工具类代码示例
package com.jsglxx.redis;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class CacheUtil {
private final RedisTemplate<String, Object> redisTemplate;
public CacheUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 添加成员到有序集合
public void addToSortedSet(String key, double score, Object member) {
redisTemplate.opsForZSet().add(key, member, score);
}
// 从有序集合中移除成员
public void removeFromSortedSet(String key, Object... members) {
redisTemplate.opsForZSet().remove(key, members);
}
// 获取成员的分值
public Double getScore(String key, Object member) {
return redisTemplate.opsForZSet().score(key, member);
}
// 获取有序集合指定范围的成员(从小到大排序)
public Set<Object> getRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
// 获取有序集合指定范围的成员(从大到小排序)
public Set<Object> getReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
// 获取分值范围内的成员(从小到大排序)
public Set<Object> getRangeByScore(String key, double minScore, double maxScore) {
return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
}
// 获取分值范围内的成员(从大到小排序)
public Set<Object> getReverseRangeByScore(String key, double maxScore, double minScore) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, maxScore, minScore);
}
// 获取有序集合的成员数量
public Long getCardinality(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
// 获取分值范围内的成员数量
public Long getCountByScore(String key, double minScore, double maxScore) {
return redisTemplate.opsForZSet().count(key, minScore, maxScore);
}
}
- addToSortedSet 方法使用 redisTemplate.opsForZSet().add(key, member, score) 向有序集合添加成员和分值。
- removeFromSortedSet 方法使用 redisTemplate.opsForZSet().remove(key, members) 从有序集合中移除指定成员。
- getScore 方法使用 redisTemplate.opsForZSet().score(key, member) 获取指定成员的分值。
- getRange 和 getReverseRange 方法分别用于获取有序集合指定范围的成员,按照分值从小到大和从大到小排序。
- getRangeByScore 和 getReverseRangeByScore 方法用于获取分值范围内的成员,同样。按照分值从小到大和从大到小排序。
- getCardinality 方法使用 redisTemplate.opsForZSet().zCard(key) 获取有序集合的成员数量。
- getCountByScore 方法使用 redisTemplate.opsForZSet().count(key, minScore, maxScore) 获取分值范围内的成员数量。
3. 测试类代码
在测试类中,首先向有序集合添加成员,然后分别测试各个方法,输出结果以验证方法的正确性。最后,从有序集合中移除一个成员。
public void testSortedSetOperations() {
String sortedSetKey = "mySortedSet";
// 添加成员到有序集合
cacheUtil.addToSortedSet(sortedSetKey, 10.0, "member1");
cacheUtil.addToSortedSet(sortedSetKey, 20.0, "member2");
cacheUtil.addToSortedSet(sortedSetKey, 15.0, "member3");
// 获取成员的分值
Double score = cacheUtil.getScore(sortedSetKey, "member2");
System.out.println("member2 的分值: " + score);
// 获取有序集合指定范围的成员(从小到大排序)
Set<Object> range = cacheUtil.getRange(sortedSetKey, 0, 1);
System.out.println("有序集合ascending(0-1)的成员从小到大排序: " + range);
// 获取有序集合指定范围的成员(从大到小排序)
Set<Object> reverseRange = cacheUtil.getReverseRange(sortedSetKey, 0, 1);
System.out.println("有序集合ascending(0-1)的成员从大到小排序: " + reverseRange);
// 获取分值范围内的成员(从小到大排序)
Set<Object> rangeByScore = cacheUtil.getRangeByScore(sortedSetKey, 12.0, 18.0);
System.out.println("ascending集合内分值在12.0 - 18.0之间的成员: " + rangeByScore);
// 获取分值范围内的成员(从大到小排序)
Set<Object> reverseRangeByScore = cacheUtil.getReverseRangeByScore(sortedSetKey, 12.0, 18.0);
System.out.println("分值12.0 - 18.0之间的成员从大到小排序: " + reverseRangeByScore);
// 获取有序集合的成员数量
Long cardinality = cacheUtil.getCardinality(sortedSetKey);
System.out.println("集合内成员数量: " + cardinality);
// 获取分值范围内的成员数量
Long countByScore = cacheUtil.getCountByScore(sortedSetKey, 10.0, 20.0);
System.out.println("10.0-20.0分值范围内的成员数量: " + countByScore);
// 从有序集合中移除成员
cacheUtil.removeFromSortedSet(sortedSetKey, "member1");
}
4. 输出结果
member2 的分值: 20.0
有序集合ascending(0-1)的成员从小到大排序: [member1, member3]
有序集合ascending(0-1)的成员从大到小排序: [member2, member3]
ascending集合内分值在12.0 - 18.0之间的成员: [member3]
分值12.0 - 18.0之间的成员从大到小排序: [member3]
集合内成员数量: 3
10.0-20.0分值范围内的成员数量: 3
三、结尾
总之,Spring Boot 3 与 Redis 的整合为开发者提供了强大的数据存储和处理能力。通过对 Redis 的五种数据结构的熟练操作,我们可以根据不同的业务需求选择合适的数据结构,实现高效的数据管理和快速的访问。无论是缓存数据、构建消息队列、存储用户信息还是进行复杂的排序操作,Redis 都能在 Spring Boot 3 应用中发挥重要作用。不断探索和优化 Redis 的使用,将有助于我们构建更加健壮、高效的应用程序,满足不断变化的业务需求。
🌟 对技术管理感兴趣 请扫码关注下方 ⬇ 【 技术管理修行】
原文地址:https://blog.csdn.net/wcblog/article/details/143430527
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!