有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。如图所示,该有序集合包含kris、mike、frank、tim、martin、tom,的分数分别是1、91、200、220、250、251,有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。
Tip:有序集合的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但考试成绩可以相同。
列表、集合和有序集合三者的异同点:
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 时间轴、消息队列等 |
集合 | 否 | 否 | 无 | 标签、社交等 |
有序集合 | 否 | 是 | 分值 | 排行榜系统、社交等 |
1)添加成员
zadd key score member [score member ...]
下面操作向有序集合user:ranking添加用户tom和他的分数251:
127.0.0.1:6379> zadd user:ranking 251 tom
(integer) 1
返回结果代表成功添加成员的个数。
有关zadd命令有两点需要注意:
Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
1.nx: member必须不存在,才可以设置成功,用于添加。
2.xx. member必须存在,才可以设置成功,用于更新。
3.ch: 返回此次操作后,有序集合元素和分数发生的个数。
4.incr: 对score做增加,相当于后面介绍的zincrby。
有序集合相比集合提供了排序字段,但也产生了代价,zadd的时间复杂度为O(log(n)),sadd的时间复杂度为O(1)。
2)计算成员个数
zcard key
和集合类型的scard一样,zcard的时间复杂度为O(1)。
127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
(integer) 5
127.0.0.1:6379> zcard user:ranking
(integer) 6
3)计算某个成员的分数
zscore key member
如果成员不存在则返回nil:
127.0.0.1:6379> zscore user:ranking tom
"251"
127.0.0.1:6379> zscore user:ranking nosee
(nil)
4)计算成员的排名
zrank key member
zrevrank key member
zrank是从分数从低到高返回排名,zrevrank反之。如下操作,tom在zrankt zrevrank分别排名第5和第0(排名从0开始计算)。
127.0.0.1:6379> zrank user:ranking tom
(integer) 5
127.0.0.1:6379> zrevrank user:ranking tom
(integer) 0
5)删除成员
zrem key member [member ...]
返回结果为删除成功的个数,如:
127.0.0.1:6379> zrem user:ranking mike
(integer) 1
6)增加成员的分数
zincrby key increment member
如:
127.0.0.1:6379> zincrby user:ranking 9 tom
"260"
7)返回指定排名范围的成员
zrange key start end [withscore]
zrevrange key start end [withscore]
有序集合是按照分值排名的,zrange是从高到低返回 ,zrevrange反之。下面代码返回排名最低的是三个成员,如果加上withscore选项,同时会返回成员的分数:
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
8)返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key min max [withscores] [limit offset count]
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如下面操作从低到高返回200到221分的成员,withscore选项会同时返回每个成员的分数。 [limit offset count]选项可以限制输出的起始位置和个数:
127.0.0.1:6379> zrangebyscore user:ranking 200 221 withscores
1) "frank"
2) "200"
3) "tim"
4) "220"
127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"
同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大:
127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "260"
9)返回指定分数范围成员个数
zcount key min max
如:
127.0.0.1:6379> zcount user:ranking 200 221
(integer) 2
10)删除指定排名内的升序元素
zremrangebyrank key start end
下面操作会删除第start到第end名的成员:
127.0.0.1:6379> zremrangebyrank user:ranking 0 2
(integer) 3
11)删除指定分数范围的成员
zremrangebyscore key min max
下面操作将250分以上的成员全部删除,返回结果为成功删除的个数:
127.0.0.1:6379> zremrangebyscore user:ranking (250 +inf
(integer) 1
将下图的两个有序集合导入到Redis中:
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom
(integer) 6
127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
(integer) 4
1)交集
zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sun|min|max]
这个命令参数较多,下面分别进行说明:
下面操作对user:ranking:1和user:ranking:2做交集,weights和aggregate使用了默认配置,可以看到目标键user:ranking:1_inter_2对分值做了sum操作:
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"
如果想让user:ranking:2的权重变为0.5,并且聚合效果使用max,可以执行如下操作:
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
2)并集
zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sun|min|max]
该命令的所有参数和zinterstore是一致的,只不过是做并集计算,例如下面操作是计算user:ranking:1和user:ranking:2的并集:
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
(integer) 7
127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
1) "kris"
2) "1"
3) "james"
4) "8"
5) "mike"
6) "168"
7) "frank"
8) "200"
9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"
命令 | 时间复杂度 |
---|---|
zadd key score member [score member …] | O(k*log(n)),k是添加成员的个数,n是当前有序集合成员个数 |
zcard key | O(1) |
zscore key member | O(1) |
zrank/zrevrank key member | O(log(n)),n是当前有序集合成员个数 |
zrem key member [member …] | O(k*log(n)),k是删除成员的个数,n是当前有序集合成员个数 |
zincrby key increment member | O(log(n)),n是当前有序集合成员个数 |
zrange/zrevrange key start end [withscore] | O(log(n)+k),k是要获取的成员个数,n是当前有序集合成员个数 |
zrangebyscore/zrevrangebyscore key min max [withscores] [limit offset count] | O(log(n)+k),k是要获取的成员个数,n是当前有序集合成员个数 |
zcount key min max | O(log(n)),n是当前有序集合成员个数 |
zremrangebyrank key start end | O(log(n)+k),k是要删除的成员个数,n是当前有序集合成员个数 |
zremrangebyscore key min max | O(log(n)+k),k是要删除的成员个数,n是当前有序集合成员个数 |
zinterstore destination numkeys key [key …] | O(n*k)+O(m*log(m)),n是成员数最小的有序集合成员个数,k是有序集合的个数,m结果集中成员个数 |
zunionstore destination numkeys key [key …] | O(n)+O(m*log(m)),n是所有有序集合成员个数和,m结果集中成员个数 |
《Redis开发与运维》 付磊 & 张益军