# 书接上回
上一篇我们学习的 set
集合这一数据类型。其内部是由 inset
和 hashtable
这种两种数据结构编码的。
如果不记得了,那就来坐穿梭机回去看看吧。 开始穿梭
接下来,我们继续学习一个新的数据类型,有序集合. zset
.
# zset
简介
zset
, 中文名字叫 有序集合。序这个字,在 Redis
的实现是 score
字段。我们先不急这个字段,后面会介绍。
在 Redis
中有序的数据类型,还有一个就是我们前面学习的 list
了。 它们还都可以获得某一定范围内的元素。
而 zset
的优点是: list
通过链表实现,在两端操作数据都很方便。但是操作中间的数据就比较慢了。 zset
是用 hashtable
和 skiplist
来实现的。即使是操作中间数据,速度也很快。时间复杂度为: O(logN)
zset
的缺点就是:就是比较耗费内存。
# zset
类型的应用场景
- 存储学生成绩快速做成绩排名功能。
- 排行榜,比如:列出某用户当前的全球排名,比赛中胜场数排名。
- 带权重的消息队列功能
# zset
的基本命令
# zadd
- 语法
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
- 解释
将 member
添加有序集合中.
如果 member
存在,会更新 member
的 score
值。
-
NX
表示存在相同的member
就会设置失败,NX 的作用就是 新增member
,不会修改Member
-
XX
表示不存在相同的member
就会设置失败。所以:XX
总是更新元素。不会新增元素 -
CH
(change
): 返回修改的元素个数。更改的元素是添加的新元素以及已为其更新分数的现有元素。因此,命令行中指定的具有与过去相同分数的元素将不计算在内。注意:通常,ZADD
的返回值仅计算添加的新元素的数量。 -
INCR
: 指定此选项后,ZADD
的行为类似于ZINCRBY
。在此模式下只能指定一对得分元素。 -
演示
1 | # 设置一个元素 |
# zscore
- 语法
ZSCORE key member
- 解释
zset score
查看对应元素的 score
值
* 当`key`不存在或者`member`不存在的时候,返回`(nil)`
返回 score
的值。
- 演示
1 | # 验证k68不存在的时候,返回nil |
# zincrby
- 语法
ZINCRBY key increment member
- 解释
zset increment by
* increment
: 步长。
* member
: 指定的成员
为有序集合 key
的成员 member
的 score
值加上 increment
。
如果 key
或者 member
不存在,则新增一个元素。相当于 zadd
.
- 演示
1 | # 插入一个不存在的key。 |
# zcard
- 语法
ZCARD key
- 解释
返回 有序集合的 key
中的元素个数。即 member
的个数。
不存在的时候,返回 0
.
- 演示
1 | 返回member的个数 |
# zcount
- 语法
ZCOUNT key min max
- 解释
返回 score
值在 min
和 max
之间的元素的个数。包括等于 min
和 max
- 演示
1 | 127.0.0.1:6379> ZADD k71 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrange
- 语法
ZRANGE key start stop [WITHSCORES]
- 解释
这个命令我们已经用过,就是返回指定开始结束位置上的元素。从 0
开始。
- 演示
1 | 127.0.0.1:6379> ZRANGE k72 1 3 |
# zrevrange
- 语法
ZREVRANGE key start stop [WITHSCORES]
- 解释
返回有序集合中指定区间的成员。
其中成员的位置按 score
值递减 (从大到小) 来排列。 具有相同 score
值的成员按字典序的逆序 ( reverse lexicographical order
) 排列。
- 演示
1 | 127.0.0.1:6379> ZADD k73 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrangebyscore
- 语法
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
- 解释
zset range by score
类似 zrange
, 不过是按照 score
的值进行排序的。
[LIMIT offset count]
, 是从 offset 开始,返回 count
个。
- 演示
1 | # 返回 score值在 [9,10]之间的member。 |
# zrevrangebyscore
- 语法
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
- 解释
返回有序集 key
中, score
值介于 max
和 min
之间 (默认包括等于 max
或 min
) 的所有的成员。有序集成员按 score
值递减 (从大到小) 的次序排列。
注意各个参数的位置哦。这里和 zrevrange
的参数不一样。
- 演示
1 | 127.0.0.1:6379> ZADD k75 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrank
- 语法
ZRANK key member
- 解释
返回有序集 key
中成员 member
的排名。其中有序集成员按 score
值递增 (从小到大) 顺序排列
- 演示
1 | 127.0.0.1:6379> ZADD k76 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrevrank
- 语法
ZREVRANK key member
- 解释
返回有序集 key
中成员 member
的排名。其中有序集成员按 score
值递减 (从大到小) 排序。
排名以 0
为底,也就是说, score
值最大的成员排名为 0
。
- 演示
1 | 127.0.0.1:6379> ZADD k77 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrem
- 语法
ZREM key member [member ...]
- 解释
zset remove
移除有序集 key
中的一个或多个成员,不存在的成员将被忽略。
当 key
存在但不是有序集类型时,返回一个错误。
- 演示
1 | 127.0.0.1:6379> ZADD k78 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zremrangebyrank
- 语法
ZREMRANGEBYRANK key start stop
- 解释
移除有序集 key
中,指定排名 ( rank
) 区间内的所有成员。
区间分别以下标参数 start
和 stop
指出,包含 start
和 stop
在内。
- 演示
1 | 127.0.0.1:6379> ZADD k79 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zremrangebyscore
- 语法
ZREMRANGEBYSCORE key min max
- 解释
移除有序集 key
中,所有 score
值介于 min
和 max
之间 (包括等于 min
或 max
) 的成员。
- 演示
1 | 127.0.0.1:6379> ZADD k80 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrangebylex
- 语法
ZRANGEBYLEX key min max [LIMIT offset count]
- 解释
当有序集合的所有成员都具有相同的分值时, 有序集合的元素会根据成员的字典序( lexicographical ordering
)来进行排序, 而这个命令则可以返回给定的有序集合键 key
中, 值介于 min
和 max
之间的成员。
注意:
合法的 min
和 max
参数必须包含 ( 或者 [
, 其中 (
表示开区间(指定的值不会被包含在范围之内), 而 [
则表示闭区间(指定的值会被包含在范围之内)。
特殊值 +
和 -
在 min
参数以及 max
参数中具有特殊的意义, 其中 +
表示正无限, 而 -
表示负无限。 因此, 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX <zset> - +
, 命令将返回有序集合中的所有元素
lex
:
表示如果score
相等,则按照member
的字典顺序排序。
此外这个命令,比如ZRANGBYSCORE
稍微强大一点儿。可以指定区间范围,当只知道member
,不知道 score 的时候,可以是使用带有lex
的命令。
- 演示
1 | 127.0.0.1:6379> zadd k81 1 a 2 b 3 c 4 d 5 f 6 g |
# zlexcount
- 语法
ZLEXCOUNT key min max
- 解释
对于一个所有成员的分值都相同的有序集合键 key
来说, 这个命令会返回该集合中, 成员介于 min
和 max
范围内的元素数量。
- 演示
1 | 127.0.0.1:6379> zadd k82 1 a 2 b 3 c 4 d 5 f 6 g |
# zremrangebylex
- 语法
ZREMRANGEBYLEX key min max
- 解释
对于一个所有成员的分值都相同的有序集合键 key
来说, 这个命令会移除该集合中, 成员介于 min
和 max
范围内的所有元素。
- 演示
1 | 127.0.0.1:6379> zadd k83 1 a 2 b 3 c 4 d 5 f 6 g |
# zscan
- 语法
ZSCAN key cursor [MATCH pattern] [COUNT count]
- 解释
这是一个查询命令。 同 SCAN
命令。可以参考这篇文章 010 - 其他命令
SCAN
命令是一个基于游标的迭代器( cursor based iterator
): SCAN
命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN
命令的游标参数, 以此来延续之前的迭代过程。
- 演示
1 | 127.0.0.1:6379> ZADD k84 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zunionstore
- 语法
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
-
解释
计算给定的一个或多个有序集合的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集 (结果集) 储存到 destination 。
如果 key 相同的时候,对应的 score 值会相加。WEIGHTS
: 使用WEIGHTS
选项,你可以为 每个 给定有序集 分别 指定一个乘法因子 (multiplication factor
),每个给定有序集的所有成员的score
值在传递给聚合函数 (aggregation function
) 之前都要先乘以该有序集的因子。AGGREGATE
: 使用AGGREGATE
选项,你可以指定并集的结果集的聚合方式。
默认使用的参数SUM
,可以将所有集合中某个成员的score
值之 和 作为结果集中该成员的score
值;使用参数MIN
,可以将所有集合中某个成员的 最小score
值作为结果集中该成员的score
值;而参数MAX
则是将所有集合中某个成员的 最大score
值作为结果集中该成员的score
值。
-
演示
1 | 127.0.0.1:6379> ZADD k85_1 1 m1 2 m2 3 m3 |
# zinterstore
- 语法
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
- 解释
计算给定的一个或多个有序集的交集,其中给定 key
的数量必须以 numkeys
参数指定,并将该交集 (结果集) 储存到 destination
。
默认情况下,结果集中某个成员的 score
值是所有给定集下该成员 score
值之和.
- 演示
1 | 127.0.0.1:6379> zadd k86_1 1 m1 2 m2 3 m3 4 m4 |
# zset
的内部结构
这里我们主要看 skiplist
,如果忘记了 hashtable
,就看着这篇文章
我们从命令 zadd
入手,找到 zset
的 add
通用方法 zaddGenericCommand(c,ZADD_NONE);
来看一下。省略了部分代码。
1 | ... |
来看一下 createZsetObject()
方法的实现,就再清晰不过了。
1 | /// 创建 Zset 对象。 |
到这里,就是我们和 zset
这种数据类型的初次深入见面了。 我们先看下 zset
这种结构体的定义。
1 | /// 有序集合的结构定义 |
dict
前面已经看过了,这里来看下 zskiplist
。
# skiplist
1 | /// 跳跃表 |
# 总结
- 本章是一个新的常用数据类型,
ZSET
有序集合。底层的数据结构是使用的是skipList
和hashtable
. 关于Skiplist
的初步了解文章穿梭机 和Redis
中skiplist
的实现源码解读穿梭机 - 然后简单介绍了
Redis ZSET
数据类型的基础使用场景。关键字有有序
,排名
,权重
等. ZSET
的20
个常有命令。 后面我会针对这20
个命令的实现进行简单的分享.- 然后简单的看了一下
Redis
中的数据结构的实现,还是那句话,Redis
的数据结构是动态编码的,ZSET
是有hashtable
和skiplist
实现的。 skiplist 是一个非常高效的数据结构,增删查的效率都是O(logN)
. 实现原理可以参考这篇文章直通车,里面有几种流行的语言的实现,可以针对自己擅长的语言进行查看。
# 最后
期望与你一起遇见更好的自己