5_zset(有序列表)
Redis 基础类型——zset(有序列表)
介绍基本概念
zset 是 Redis 提供的最有特色的数据结构,它是面试官最爱问的数据结构。
它类似 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。
内部用“跳跃列表”的数据结构实现
zset 中最后一个元素被移除后,数据结构会被自动删除,内存被回收。
zset 可以用来存储粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。可以对粉丝列表按关注时间进行排序。
命令与 Java 方法对应关系
| 命令 | Java 方法 | 描述 |
|---|---|---|
ZADD key score1 member1[score2 member2] | add(key,value,score) | 将一个或多个 member 元素及其 score 值加入到有序集 key 当中 |
ZCARD key | zCard(key) | 返回有序集 key 对应的成员总数 |
ZCOUNT key min max | count(key, score1, score2) | 返回有序集 key 中,score 值在 min 和 max 之间成员数量 |
ZRANGE key start stop | range(key, index1, index2) | 返回有序集 key 中,指定区间内的成员,索引从 0 开始 |
ZRANGEBYSCORE key min max | rangeByScore(key, score1, score2) | 返回有序集 key 中,所有 score 值区间内(包含)的成员 |
ZREVRANGE | reverseRange(key, index1 , index2) | 与 ZRANGE 排序规则相反 |
ZREVRANGEBYSCORE key max min WITHSCORES LIMIT offset count | reverseRangeByScore(key, score1, score2) | 与 ZRANGEBYSCORE 排序规则相反 |
ZINCRBY key increment member | incrementScore(key, value, increment) | 为有序集 key 的成员 member 的 score 值加上增量 increment |
ZRANK key member | 返回有序集合中指定成员的索引 | |
ZREM key member | remove(key,value) | 移除有序集 key 中的一个或多个成员 |
ZREMRANGEBYLEX key min max | 移除有序集合中给定的字典区间的所有成员 | |
ZREMRANGEBYRANK key start stop | 移除有序集合中给定的排名区间的所有成员 | |
ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 | |
ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减 (从大到小) 排序 | |
ZSCORE key member | 返回有序集中,成员的分数值 | |
| `ZSCAN key cursor [MATCH pattern][COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
使用注意事项
获取指定 value 的 score,内部的 score 使用 double 类型存储,所有存在小数点精度问题
跳跃列表
zset 内部的排序功能是通过“跳跃列表”数据结构来实现的。
以为 zset 要支持随机的插入和删除,所以它不宜使用数字来表示。
我们需要链表按照 score 值进行排序。这意味着当有新元素需要插入式,要定位到特定位置的插入点,这样可以继续保证链表的有序。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到怎么办?
跳跃列表就类似于层级制,最下面一层所有元素都会串联起来。然后每隔几个元素挑选出一个代表,再将这几个代表使用另外一层指针串起来,表示一层。这些一层代表里再挑选出二级代表,再串起来。最终形成金字塔结构。
跳跃列表采取一个随机策略来决定新元素可以兼职到几层。
首先其位于 L0 层的概率肯定 100%,而兼职 L1 层只有 50% 的概率,到 L2 层只有 25% 的概率,到 L3 层只有 12.5% 的概率,依次类推,一直到最顶层 L31 层。
绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,元素能进入到顶层的可能性就越大。
关于这块算法可以参考左神这个视频 1小时31分54秒开始的关于跳表的讲述 个人觉得他们是一个原理
ZRANGEBYSCORE 详解
简介
返回有序集 key 中,所有 score 值介于 min 和 max 之间 (包括等于 min 或 max ) 的成员。有序集成员按 score 值递增 (从小到大) 次序排列。
具有相同 score 值的成员按字典序 (lexicographical order) 来排列 (该属性是有序集提供的,不需要额外的计算)。
语法
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
可选的 LIMIT 参数指定返回结果的数量及区间 (就像 SQL 中的 SELECT LIMIT offset, count ),注意当 offset 很大时,定位 offset 的操作可能需要遍历整个有序集,此过程最坏复杂度为 O(N) 时间。
可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 score 值一起返回。该选项自 Redis 2.0 版本起可用。
区间及无限
min 和 max 可以是 -inf 和 +inf ,这样一来,你就可以在不知道有序集的最低和最高 score 值的情况下,使用 ZRANGEBYSCORE 这类命令。
默认情况下,区间的取值使用 闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的 开区间 (小于或大于)。
举个例子:
ZRANGEBYSCORE zset (1 5使用示例
redis> ZADD salary 2500 jack # 测试数据
(integer) 0
redis> ZADD salary 5000 tom
(integer) 0
redis> ZADD salary 12000 peter
(integer) 0
redis> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "jack"
2) "tom"
3) "peter"
redis> ZRANGEBYSCORE salary -inf +inf WITHSCORES # 显示整个有序集及成员的 score 值
1) "jack"
2) "2500"
3) "tom"
4) "5000"
5) "peter"
6) "12000"
redis> ZRANGEBYSCORE salary -inf 5000 WITHSCORES # 显示工资 <=5000 的所有成员
1) "jack"
2) "2500"
3) "tom"
4) "5000"
redis> ZRANGEBYSCORE salary (5000 400000 # 显示工资大于 5000 小于等于 400000 的成员
1) "peter"ZREVRANGEBYSCORE 详解
语法
ZREVRANGEBYSCORE key max min WITHSCORES LIMIT offset count
基本与上面 ZRANGEBYSCORE 一致,注意 max 与 min 位置相反
ZRANGEBYLEX 详解
简介
ZRANGEBYLEX 返回指定成员区间内的成员
此指令适用于分数相同的有序集合中。LEX 结尾的指令是要求分数必须相同
语法
ZRANGEBYLEX key min max [LIMIT offset count]
说明
| 指令 | 是否必须 | 说明 |
|---|---|---|
| ZRANGEBYLEX | 是 | 指令 |
| key | 是 | 有序集合键名称 |
| min | 是 | 字典中排序位置较小的成员,必须以”[“开头,或者以”(“开头,可使用”-“代替 |
| max | 是 | 字典中排序位置较大的成员,必须以”[“开头,或者以”(“开头,可使用”+”代替 |
| LIMIT | 否 | 返回结果是否分页,指令中包含 LIMIT 后 offset、count 必须输入 |
| offset | 否 | 返回结果起始位置 |
| count | 否 | 返回结果数量 |
使用注意
- 分数必须相同!如果有序集合中的成员分数有不一致的,返回的结果就不准。
- 成员字符串作为二进制数组的字节数进行比较。
- 默认是以 ASCII 字符集的顺序进行排列。如果成员字符串包含 utf-8 这类字符集的内容,就会影响返回结果,所以建议不要使用。
- 默认情况下, “max” 和 “min” 参数前必须加 “
[” 符号作为开头。”[” 符号与成员之间不能有空格,返回成员结果集会包含参数 “min” 和 “max” 。 - “max” 和 “min” 参数前可以加 “
(” 符号作为开头表示小于,“(” 符号与成员之间不能有空格。返回成员结果集不会包含 “max” 和 “min” 成员。 - 可以使用 “
-” 和 “+” 表示得分最小值和最大值 - “min” 和 “max” 不能反,“max” 放前面 “min”放后面会导致返回结果为空
- 与 ZRANGEBYLEX 获取顺序相反的指令是 ZREVRANGEBYLEX。
- 源码中采用 C 语言中 memcmp() 函数,从字符的第 0 位到最后一位进行排序,如果前面部分相同,那么较长的字符串比较短的字符串排序靠后。
示例
准备数据
zadd key1 0 a 0 aa 0 abc 0 apple 0 b 0 c 0 d 0 d1 0 dd 0 dobble 0 z 0 z1
获取所有值
zrangebylex key1 - +
1) "a"
2) "aa"
3) "abc"
4) "apple"
5) "b"
6) "c"
7) "d"
8) "d1"
9) "dd"
10) "dobble"
11) "z"
12) "z1"获取分页数据
redis> ZRANGEBYLEX key1 - + LIMIT 0 3
1) "a"
2) "aa"
3) "abc"
redis> ZRANGEBYLEX key1 - + LIMIT 3 3
1) "apple"
2) "b"
3) "c"获取成员之间的元素
默认情况下, “max” 和 “min” 参数前必须加 “[” 符号作为开头。
“[” 符号与成员之间不能有空格, 返回成员结果集会包含参数 “min” 和 “max” 。
不能反,“max” 放前面 “min”放后面会导致返回结果为空
redis> ZRANGEBYLEX key1 [aa [c
1) "aa"
2) "abc"
3) "apple"
4) "b"
5) "c"使用 “(” 小于号获取成员之间的元素
max” 和 “min” 参数前可以加 “(” 符号作为开头表示小于, “(” 符号与成员之间不能有空格。
返回成员结果集不会包含 “max” 和 “min” 成员。
redis> ZRANGEBYLEX key1 [aa (c
1) "aa"
2) "abc"
3) "apple"
4) "b"ASCII 码的影响
成员字符串作为二进制数组的字节数进行比较。
默认是以 ASCII 字符集的顺序进行排列。
如果成员字符串包含 utf-8 这类字符集的内容,就会影响返回结果,所以建议不要使用。
redis> zadd key1 0 aBc
(integer) 1
redis> ZRANGEBYLEX key1 - +
1) "a"
2) "aBc"
3) "aa"
4) "abc"
5) "apple"
6) "b"
7) "c"
8) "d"
9) "d1"
10) "dd"
11) "dobble"
12) "z"
13) "z1"
redis> ZRANGEBYLEX key1 - + LIMIT 0 3
1) "a"
2) "aBc"
3) "aa"使用场景示例
电话号码排序
我们可以将电话号码存储到 SortSet 中,然后根据需要来获取号段:
redis> zadd phone 0 13100111100 0 13110114300 0 13132110901
(integer) 3
redis> zadd phone 0 13200111100 0 13210414300 0 13252110901
(integer) 3
redis> zadd phone 0 13300111100 0 13310414300 0 13352110901
(integer) 3获取所有号码:
redis> ZRANGEBYLEX phone - +
1) "13100111100"
2) "13110114300"
3) "13132110901"
4) "13200111100"
5) "13210414300"
6) "13252110901"
7) "13300111100"
8) "13310414300"
9) "13352110901"获取 132 号段:
redis> ZRANGEBYLEX phone [132 (133
1) "13200111100"
2) "13210414300"
3) "13252110901"获取 132、133 号段:
redis> ZRANGEBYLEX phone [132 (134
1) "13200111100"
2) "13210414300"
3) "13252110901"
4) "13300111100"
5) "13310414300"
6) "13352110901"