Redis值数据类型——sorted set

4.5 sorted set

4.5.1 概述

  1. 核心基础
    有序集合(sorted set)是在普通集合(set)的基础上做了扩展,为集合中的每一个元素都关联了一个对应的分数(score)。
    基于这个特性,它不仅支持普通集合的核心操作(插入、删除、判断元素是否存在),还额外支持一系列与分数相关的专属操作(如获取分数最高 / 最低的前 N 个元素、获取指定分数范围的元素等)。
  2. 与列表(list)类型的异同
    (一)相同点(二者均具备有序性、范围查询能力)
    二者存储的元素都是有序的;
    二者都支持获取指定范围的元素。
    (二)核心区别(底层实现与功能特性差异)

|--------|--------------------------------|-----------------------------|
| 对比维度 | 列表(list) | 有序集合(sorted set) |
| 底层实现 | 双向链表 | 散列表 |
| 访问性能 | 靠近两端的数据访问速度极快,元素增多后,访问中间数据速度变慢 | 无论首尾还是中间数据,访问速度都很快(高效) |
| 元素位置调整 | 无法简单调整单个元素的位置 | 可通过 修改元素关联的分数 ,灵活调整元素位置 |
| 内存消耗 | 内存占用相对较低 | 内存消耗更高(因需额外存储分数及维护有序结构) |

  1. 总结
  • sorted set 基于普通 set 扩展,核心特性是「元素唯一 + 关联分数 + 有序排列」;
  • 与 list 同为有序且支持范围查询,但底层实现、访问性能、功能灵活性、内存消耗均有显著差异;
  • sorted set 更适合需要基于分数排序、高效查询中间数据的场景,代价是更高的内存占用。

4.5.2 命令

  1. 增加元素
    向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。

    ZADD key score member [score member ...]

2.获取元素的分数

复制代码
ZSCORE key member
  1. 获取排名在某个范围的元素列表
  • 按照元素分数从小到大的顺序返回索引从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]

  1. 获得指定分数范围的元素

    ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count]


(min / (max:给 min 或 max 加上括号,代表不包含该边界值(默认是包含边界值的)。
offset:是在符合分数范围的结果列表里的起始下标(从 0 开始)。

  1. 增加某个元素的分数
    返回值是更改后的分数

    ZINCRBY key increment member

6.获得集合中元素的数量

复制代码
ZCARD key

7.获得指定分数范围内的元素个数

复制代码
ZCOUNT key min max

8.按照排名范围删除元素

按分数从小到大(升序)排序

复制代码
ZREMRANGEBYRANK key start stop
  1. 按照分数范围删除元素

    ZREMRANGEBYSCORE key min max

10.获取元素的排名

复制代码
#从小到大
ZRANK key member
#从大到小
ZREVRANK key member


4.5.3 方法

  1. 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 开始) |

  1. 总结
    SpringBoot 中操作 Redis Sorted Set 的核心套路是 stringRedisTemplate.opsForZSet().xxx()。
  2. 代码
复制代码
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();
    }
}

}
相关推荐
云姜.2 小时前
Redis 缓存穿透/缓存雪崩/缓存击穿问题
redis·缓存·bootstrap
漫霂2 小时前
关注推送-Feed流
redis
supericeice2 小时前
GraphRAG 和 RAG 的区别:企业知识问答什么时候该升级到 GraphRAG
数据库·知识图谱·rag·graphrag
菜菜小狗的学习笔记2 小时前
黑马程序员Redis--基础篇
数据库·redis·缓存
是桃萌萌鸭~2 小时前
Oracle参数db_unique_name详解
数据库·sql·oracle·database
Binary-Jeff2 小时前
MySQL MVCC 原理解析:Undo Log、ReadView 与版本可见性机制
java·数据库·后端·mysql·spring
bug远离Jemma2 小时前
MySql基本使用命令记录
数据库·mysql·oracle
Leon-Ning Liu2 小时前
SQL Server在ldf文件误删的情况下恢复数据库
数据库·sqlserver
专注_每天进步一点点2 小时前
mysql-connector-j(8.0 及以上版本,包括你使用的 8.3.0)并非采用 GPL 许可证,因此你在项目中引入该依赖时,不需要遵循 GPL 的开源要求(比如开源你的整个项目)
数据库·mysql·apache