Redis值数据类型——sorted set
4.5 sorted set
4.5.1 概述
- 核心基础
有序集合(sorted set)是在普通集合(set)的基础上做了扩展,为集合中的每一个元素都关联了一个对应的分数(score)。
基于这个特性,它不仅支持普通集合的核心操作(插入、删除、判断元素是否存在),还额外支持一系列与分数相关的专属操作(如获取分数最高 / 最低的前 N 个元素、获取指定分数范围的元素等)。
- 与列表(list)类型的异同
(一)相同点(二者均具备有序性、范围查询能力)
二者存储的元素都是有序的;
二者都支持获取指定范围的元素。
(二)核心区别(底层实现与功能特性差异)
对比维度 | 列表(list) | 有序集合(sorted set) |
底层实现 | 双向链表 | 散列表 |
访问性能 | 靠近两端的数据访问速度极快,元素增多后,访问中间数据速度变慢 | 无论首尾还是中间数据,访问速度都很快(高效) |
元素位置调整 | 无法简单调整单个元素的位置 | 可通过 修改元素关联的分数 ,灵活调整元素位置 |
内存消耗 | 内存占用相对较低 | 内存消耗更高(因需额外存储分数及维护有序结构) |
- 总结
- sorted set 基于普通 set 扩展,核心特性是「元素唯一 + 关联分数 + 有序排列」;
- 与 list 同为有序且支持范围查询,但底层实现、访问性能、功能灵活性、内存消耗均有显著差异;
- sorted set 更适合需要基于分数排序、高效查询中间数据的场景,代价是更高的内存占用。
4.5.2 命令
- 增加元素
向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。
ZADD key score member [score member ...]2.获取元素的分数
ZSCORE key member- 获取排名在某个范围的元素列表
- 按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。
ZRANGE key start stop [WITHSCORES]先将该有序集合的所有元素,按照分数(score)从小到大(升序)进行排序,形成一个有序的 “虚拟列表”;
这个排序后的 “虚拟列表”下标从 0 开始(和 Redis List、Java 数组下标规则一致);
按照 start 和 stop 指定的下标范围,返回该范围内的所有元素(包含 start 和 stop 对应的元素);
可选参数 WITHSCORES:加上该参数后,返回结果会同时携带元素的值(member)和对应的分数(score),不加则只返回元素值。
- 照元素分数从大到小的顺序返回索引从start到stop之间的所有元素(包含两端的元素)
ZREVRANGE key start stop [WITHSCORES]- 获得指定分数范围的元素
ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count](min / (max:给 min 或 max 加上括号,代表不包含该边界值(默认是包含边界值的)。
offset:是在符合分数范围的结果列表里的起始下标(从 0 开始)。
- 增加某个元素的分数
返回值是更改后的分数
ZINCRBY key increment member6.获得集合中元素的数量
ZCARD key7.获得指定分数范围内的元素个数
ZCOUNT key min max8.按照排名范围删除元素
按分数从小到大(升序)排序
ZREMRANGEBYRANK key start stop- 按照分数范围删除元素
ZREMRANGEBYSCORE key min max10.获取元素的排名
#从小到大 ZRANK key member #从大到小 ZREVRANK key member4.5.3 方法
- sorted set类型核心操作
Redis 命令 | StringRedisTemplate 核心方法 | 功能说明 |
zadd | opsForZSet().add(key, member, score) | 添加 / 更新有序集合元素 |
zscore | opsForZSet().score(key, member) | 获取元素对应的分数 |
zrange | opsForZSet().range(key, start, stop) | 升序获取指定排名范围元素 |
zrange withscores | opsForZSet().rangeWithScores(key, start, stop) | 升序获取元素并携带分数 |
zrevrange | opsForZSet().reverseRange(key, start, stop) | 降序获取指定排名范围元素 |
zrangebyscore | opsForZSet().rangeByScore(key, min, max) | 按分数范围获取元素 |
zrangebyscore withscores | opsForZSet().rangeByScoreWithScores(key, min, max) | 按分数范围获取元素并携带分数 |
zincrby | opsForZSet().incrementScore(key, member, increment) | 给元素增加指定分数 |
zcard | opsForZSet().size(key) | 获取有序集合元素总数 |
zcount | opsForZSet().count(key, min, max) | 获取指定分数范围的元素个数 |
zrem | opsForZSet().remove(key, member) | 删除有序集合中的指定元素 |
zremrangebyrank | opsForZSet().removeRange(key, start, stop) | 按排名范围删除元素 |
zremrangebyscore | opsForZSet().removeRangeByScore(key, min, max) | 按分数范围删除元素 |
zrank | opsForZSet().rank(key, member) | 升序获取元素的排名(从 0 开始) |
zrevrank | opsForZSet().reverseRank(key, member) | 降序获取元素的排名(从 0 开始) |
- 总结
SpringBoot 中操作 Redis Sorted Set 的核心套路是 stringRedisTemplate.opsForZSet().xxx()。
- 代码
package com.qcby.springbootredis; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import java.util.Set; import java.util.stream.Collectors; @SpringBootTest public class SortedSetTest { @Autowired private StringRedisTemplate stringRedisTemplate; @Test public void testSortedSetCoreCommands() { String scoreboardKey = "scoreboard"; String hackersKey = "hackers"; String zsetKey = "zset"; try { // 10.2.1 增加元素(ZADD)+ 获取元素分数(ZSCORE) Boolean add1 = stringRedisTemplate.opsForZSet().add(scoreboardKey, "zhangsan", 80); stringRedisTemplate.opsForZSet().add(scoreboardKey, "lisi", 89); stringRedisTemplate.opsForZSet().add(scoreboardKey, "wangwu", 94); System.out.println("新增 zhangsan:" + add1); Boolean add2 = stringRedisTemplate.opsForZSet().add(scoreboardKey, "lisi", 97); System.out.println("更新 lisi 分数:" + add2); Double lisiScore = stringRedisTemplate.opsForZSet().score(scoreboardKey, "lisi"); System.out.println("lisi 的分数:" + lisiScore); // 10.2.2 获得排名范围的元素列表(ZRANGE / ZREVRANGE) Set<String> zrangeResult = stringRedisTemplate.opsForZSet().range(scoreboardKey, 0, 2); System.out.println("升序排名 0-2 元素:" + zrangeResult); Set<ZSetOperations.TypedTuple<String>> zrangeWithScores = stringRedisTemplate.opsForZSet().rangeWithScores(scoreboardKey, 0, 1); Set<String> withScoresStr = zrangeWithScores.stream() .map(t -> t.getValue() + "(" + t.getScore() + ")") .collect(Collectors.toSet()); System.out.println("升序带分数:" + withScoresStr); Set<String> zrevrangeResult = stringRedisTemplate.opsForZSet().reverseRange(scoreboardKey, 0, 2); System.out.println("降序排名 0-2 元素:" + zrevrangeResult); // 10.2.3 获得指定分数范围的元素(ZRANGEBYSCORE) Set<ZSetOperations.TypedTuple<String>> zrangeByScore1 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores(scoreboardKey, 90, 97); Set<String> scoreWithStr1 = zrangeByScore1.stream() .map(t -> t.getValue() + "(" + t.getScore() + ")") .collect(Collectors.toSet()); System.out.println("分数 90-97 带分数:" + scoreWithStr1); // 半开区间 90 <= score < 97 Set<ZSetOperations.TypedTuple<String>> zrangeByScore2 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores(scoreboardKey, 90, 96.999); Set<String> scoreWithStr2 = zrangeByScore2.stream() .map(t -> t.getValue() + "(" + t.getScore() + ")") .collect(Collectors.toSet()); System.out.println("分数 90-(97) 带分数:" + scoreWithStr2); // 分页 LIMIT 1 2 Set<String> zrangeByScore3 = stringRedisTemplate.opsForZSet().rangeByScore(scoreboardKey, 70, 100, 1, 2); System.out.println("分数 70-100 分页:" + zrangeByScore3); // 10.2.4 增加元素分数(ZINCRBY)+ 集合元素数量(ZCARD)+ 分数范围元素个数(ZCOUNT) Double newLisiScore = stringRedisTemplate.opsForZSet().incrementScore(scoreboardKey, "lisi", 4); System.out.println("lisi 加4分后:" + newLisiScore); Long zcardCount = stringRedisTemplate.opsForZSet().size(scoreboardKey); System.out.println("集合元素个数:" + zcardCount); Long zcount = stringRedisTemplate.opsForZSet().count(scoreboardKey, 80, 90); System.out.println("分数 80-90 元素个数:" + zcount); stringRedisTemplate.opsForZSet().add(hackersKey, "Alan Kay", 1940); stringRedisTemplate.opsForZSet().add(hackersKey, "Richard Stallman", 1953); stringRedisTemplate.opsForZSet().add(hackersKey, "Yukihiro Matsumoto", 1965); stringRedisTemplate.opsForZSet().add(hackersKey, "Claude Shannon", 1916); stringRedisTemplate.opsForZSet().add(hackersKey, "Linus Torvalds", 1969); stringRedisTemplate.opsForZSet().add(hackersKey, "Alan Turing", 1912); Set<String> hackersZrange = stringRedisTemplate.opsForZSet().range(hackersKey, 0, -1); System.out.println("hackers 升序:" + hackersZrange); Set<String> hackersZrevrange = stringRedisTemplate.opsForZSet().reverseRange(hackersKey, 0, -1); System.out.println("hackers 降序:" + hackersZrevrange); // 清空当前数据库 stringRedisTemplate.getConnectionFactory().getConnection().flushDb(); System.out.println("清空数据库成功"); // zset 示例 stringRedisTemplate.opsForZSet().add(zsetKey, "hello", 10.1); stringRedisTemplate.opsForZSet().add(zsetKey, ":", 10.0); stringRedisTemplate.opsForZSet().add(zsetKey, "zset", 9.0); stringRedisTemplate.opsForZSet().add(zsetKey, "zset!", 11.0); Long zsetCard = stringRedisTemplate.opsForZSet().size(zsetKey); System.out.println("zset 元素个数:" + zsetCard); Double zsetScore = stringRedisTemplate.opsForZSet().score(zsetKey, "zset"); System.out.println("zset 的分数:" + zsetScore); Set<String> zsetZrange = stringRedisTemplate.opsForZSet().range(zsetKey, 0, -1); System.out.println("zset 升序:" + zsetZrange); Long zremCount = stringRedisTemplate.opsForZSet().remove(zsetKey, "zset!"); System.out.println("删除 zset!:" + zremCount); Long zsetZcount = stringRedisTemplate.opsForZSet().count(zsetKey, 9.5, 10.5); System.out.println("分数 9.5-10.5 元素个数:" + zsetZcount); Set<String> zsetFinal = stringRedisTemplate.opsForZSet().range(zsetKey, 0, -1); System.out.println("zset 删除后:" + zsetFinal); // 10.2.5 其他命令(ZREMRANGEBYRANK / ZREMRANGEBYSCORE / ZRANK / ZREVRANK) stringRedisTemplate.opsForZSet().add(scoreboardKey, "zhangsan", 80); stringRedisTemplate.opsForZSet().add(scoreboardKey, "lisi", 97); stringRedisTemplate.opsForZSet().add(scoreboardKey, "wangwu", 94); Long zremByRankCount = stringRedisTemplate.opsForZSet().removeRange(scoreboardKey, 0, 1); System.out.println("按排名删除 0-1:" + zremByRankCount); Set<String> scoreboardAfterRankDel = stringRedisTemplate.opsForZSet().range(scoreboardKey, 0, -1); System.out.println("删除后元素:" + scoreboardAfterRankDel); stringRedisTemplate.opsForZSet().add(scoreboardKey, "zhangsan", 84); Long zremByScoreCount = stringRedisTemplate.opsForZSet().removeRangeByScore(scoreboardKey, 80, 100); System.out.println("按分数删除 80-100:" + zremByScoreCount); stringRedisTemplate.opsForZSet().add(scoreboardKey, "zhangsan", 80); stringRedisTemplate.opsForZSet().add(scoreboardKey, "lisi", 97); Long lisiRank = stringRedisTemplate.opsForZSet().rank(scoreboardKey, "lisi"); System.out.println("lisi 升序排名:" + lisiRank); Long zhangsanRevRank = stringRedisTemplate.opsForZSet().reverseRank(scoreboardKey, "zhangsan"); System.out.println("zhangsan 降序排名:" + zhangsanRevRank); } catch (Exception e) { e.printStackTrace(); } } }