Spring Boot 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() 方法
publicvoidsetString(String key,String value){ redisTemplate.opsForValue().set(key, value);}这个方法用于将一个字符串值存储到 Redis 中。通过redisTemplate.opsForValue()获取到针对字符串类型的操作对象,然后调用set方法,传入键和值,将指定的值存储到 Redis 中,与该键对应。如果键已经存在,会覆盖原有值。
3. redisTemplate.opsForValue().get() 方法
publicStringgetString(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方法,传入键和要插入的元素值。随着元素不断插入,列表的头部不断变化,最先插入的元素会逐渐移向列表的尾部。
publicvoidleftPush(String key,Object value){ redisTemplate.opsForList().leftPush(key, value);}(2)依次向 list 中存入元素aaa,bbb,ccc,aaa,ddd:
@PostMapping("/leftPush")publicvoidleftPush(){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)该方法是从列表右侧插入元素:
publicvoidrightPush(String key,Object value){ redisTemplate.opsForList().rightPush(key, value);}@PostMapping("/rightPush")publicvoidrightPush(){String eee ="eee", fff="fff"; userService.rightPush(eee); userService.rightPush(fff);}(2)查看 redis中 的元素,eee和fff是从列表右侧插入:

4. 从列表左侧和右侧取出元素
(1)可以从列表左侧或者右侧取出(删除)一个元素。
即使用leftPop和rightPop方法,从列表的左侧或右侧移除并返回一个元素。如果列表为空,将返回 null。
/* * 从左侧取出一个元素 */publicObjectleftPop(String key){return redisTemplate.opsForList().leftPop(key);}/* * 从右侧取出一个元素 */publicObjectrightPop(String key){return redisTemplate.opsForList().rightPop(key);}(2)从左侧取出了元素ddd, 从右侧取出了元素fff:

5. 获取列表长度
(1)redisTemplate.opsForList().size()方法:
// 获取列表长度publicLonglistLength(String key){return redisTemplate.opsForList().size(key);}(2)请求结果为5:

6. 获取列表指定范围的元素
(1)以下方法的作用是获取指定键对应列表中从start索引到end索引范围内的元素。
它通过调用 redisTemplate.opsForList().range(key, start, end) 实现,可用于获取列表的一部分进行处理或展示,其中列表的第一个元素从 0 开始。
// 获取列表指定范围的元素publicList<Object>range(String key,long start,long end){return redisTemplate.opsForList().range(key, start, end);}(2)Redis 列表的原数据如下图所示:

(3)比如获取列表从索引 1(第二个元素) 开始 至 索引 4:
/* * 获取列表指定范围元素 */@GetMapping("range")publicList<Object>range(String key){return userService.range(key,1,4);}得出如下结果:

(4)获取整个列表
输入起始位置为 0,
/* * 获取列表指定元素 */@GetMapping("range")publicList<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 集合的不同操作:
packagecom.jsglxx.redis;importjava.util.List;importjava.util.Set;importjava.util.concurrent.TimeUnit;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Component;@ComponentpublicclassCacheUtil{privatefinalRedisTemplate<String,Object> redisTemplate;publicCacheUtil(RedisTemplate<String,Object> redisTemplate){this.redisTemplate = redisTemplate;}// 向集合中添加元素publicvoidaddToSet(String key,Object value){ redisTemplate.opsForSet().add(key, value);}// 从集合中移除元素publicvoidremoveFromSet(String key,Object value){ redisTemplate.opsForSet().remove(key, value);}// 判断元素是否在集合中publicbooleanisMemberOfSet(String key,Object value){return redisTemplate.opsForSet().isMember(key, value);}// 获取集合中的所有元素publicSet<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. 测试方法:
在以下测试方法中,首先向两个集合中添加元素,然后分别测试各个方法,输出结果以验证方法的正确性。
publicvoidtestSet(){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 工具类代码示例
packagecom.jsglxx.redis;importjava.util.List;importjava.util.Map;importjava.util.Set;importjava.util.concurrent.TimeUnit;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Component;@ComponentpublicclassCacheUtil{privatefinalRedisTemplate<String,Object> redisTemplate;publicCacheUtil(RedisTemplate<String,Object> redisTemplate){this.redisTemplate = redisTemplate;}// 设置哈希字段的值publicvoidsetHashField(String key,String field,Object value){ redisTemplate.opsForHash().put(key, field, value);}// 获取哈希字段的值publicObjectgetHashField(String key,String field){return redisTemplate.opsForHash().get(key, field);}// 删除哈希字段publicvoiddeleteHashField(String key,String... fields){ redisTemplate.opsForHash().delete(key, fields);}// 获取哈希的所有字段名publicSet<Object>getHashKeys(String key){return redisTemplate.opsForHash().keys(key);}// 获取哈希的所有值publicList<Object>getHashValues(String key){return redisTemplate.opsForHash().values(key);}// 获取哈希的所有字段和值publicMap<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. 测试类代码
在测试类中,首先设置哈希表的字段值,然后分别测试各个方法,输出结果以验证方法的正确性。
publicvoidtestHash(){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 工具类代码示例
packagecom.jsglxx.redis;importjava.util.List;importjava.util.Map;importjava.util.Set;importjava.util.concurrent.TimeUnit;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Component;@ComponentpublicclassCacheUtil{privatefinalRedisTemplate<String,Object> redisTemplate;publicCacheUtil(RedisTemplate<String,Object> redisTemplate){this.redisTemplate = redisTemplate;}// 添加成员到有序集合publicvoidaddToSortedSet(String key,double score,Object member){ redisTemplate.opsForZSet().add(key, member, score);}// 从有序集合中移除成员publicvoidremoveFromSortedSet(String key,Object... members){ redisTemplate.opsForZSet().remove(key, members);}// 获取成员的分值publicDoublegetScore(String key,Object member){return redisTemplate.opsForZSet().score(key, member);}// 获取有序集合指定范围的成员(从小到大排序)publicSet<Object>getRange(String key,long start,long end){return redisTemplate.opsForZSet().range(key, start, end);}// 获取有序集合指定范围的成员(从大到小排序)publicSet<Object>getReverseRange(String key,long start,long end){return redisTemplate.opsForZSet().reverseRange(key, start, end);}// 获取分值范围内的成员(从小到大排序)publicSet<Object>getRangeByScore(String key,double minScore,double maxScore){return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);}// 获取分值范围内的成员(从大到小排序)publicSet<Object>getReverseRangeByScore(String key,double maxScore,double minScore){return redisTemplate.opsForZSet().reverseRangeByScore(key, maxScore, minScore);}// 获取有序集合的成员数量publicLonggetCardinality(String key){return redisTemplate.opsForZSet().zCard(key);}// 获取分值范围内的成员数量publicLonggetCountByScore(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. 测试类代码
在测试类中,首先向有序集合添加成员,然后分别测试各个方法,输出结果以验证方法的正确性。最后,从有序集合中移除一个成员。
publicvoidtestSortedSetOperations(){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] 集合内成员数量:310.0-20.0分值范围内的成员数量:3三、结尾
总之,Spring Boot 3 与 Redis 的整合为开发者提供了强大的数据存储和处理能力。通过对 Redis 的五种数据结构的熟练操作,我们可以根据不同的业务需求选择合适的数据结构,实现高效的数据管理和快速的访问。无论是缓存数据、构建消息队列、存储用户信息还是进行复杂的排序操作,Redis 都能在 Spring Boot 3 应用中发挥重要作用。不断探索和优化 Redis 的使用,将有助于我们构建更加健壮、高效的应用程序,满足不断变化的业务需求。
🌟 对技术管理感兴趣 请扫码关注下方 ⬇ 【 技术管理修行】