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"