有序集合类型
看名字我们就知道,有序集合也是一种集合,并且这个集合还是有序的。列表也是有序的,那它和有序集合又有什么不同呢?有序集合的有序和列表的有序是不同的。列表中的有序指的的是插入元素的顺序和查询元素的顺序相同,而有序集合中的有序指的是它会为每个元素设置一个分数(score),而查询时可以通过分数计算元素的排名,然后再返回结果。因为有序集合也是集合类型,所以有序集合中也是不插入重复元素的,但在有序集合中分数则是可以重复,那如果在有序集合中有多个元素的分数是相同的,这些重复元素的排名是怎么计算的呢?后边我们再做详细说明。
下面先看一下列表、集合、有序集合三种数据类型之间的区别:
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 | 应用场景 |
列表 | 是 | 是 | 索引下标 | 时间轴、消息队列 |
集合 | 否 | 否 | 无 | 标签、社交 |
有序集合 | 否 | 是 | 分数 | 排行榜、社交 |
命令
1.集合内操作
- 添加元素
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
zadd 命令也是有返回值的,返回值就是当前 zadd 命令成功添加元素的个数。zadd 命令有很多选填参数:
- nx: 元素必须不存在时,才可以设置成功。
- xx: 元素必须存在时,才可以设置成功。
- ch: 返回此命令执行完成后,有序集合元素和分数发生变化的个数
- incr: 对 score 做增加。
备注:由于有序集合相比集合提供了排序字段,正是因为如此也付出了相应的代价,sadd 的时间复杂度为 O(1),而 zadd 的时间复杂度为O(log(n))。
- 计算成员个数
zcard key
- 计算某个成员的分数
zscore key member
在使用 zscore 命令时,如果 key 不存在,或者元素不存在时,该命令返回的都是(nil)。
- 计算成员的排名
zrank key member zrevrank key member
zrank 命令是从分数低到高排名,而 zrevrank 命令则恰恰相反,从高到低排名。有一点要特别注意, zrank 和 zrevrank 命令与 zscore 是命令不同的,前者通过分数计算出最后的排名,而后者则是直接返回当前元素的分数。
- 删除元素
zrem key member [member ...]
返回的结果为成功删除元素的个数,因为 zrem 命令是支持批量删除的。
- 增加元素分数
zincrby key increment member
虽然 zincrby 命令是增加元素分数的,但我们也可以指定负数,这样当前元素的分数,则会相减。
- 返回指定排名范围的元素
zrange key start stop [WITHSCORES] zrevrange key start stop [WITHSCORES]
zrange 命令是通过分数从低到高返回数据,而 zrevrange 命令是通过分数从高到低返回数据。如果执行命令时添加了 WITHSCORES 可选参数,则返回数据时会返回当前元素的分数。
- 返回指定分数范围的元素
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
min 和 max 参数还支持开区间(小括号)和闭区间(中括号),同时我们还可以用 -inf 和 +inf 参数代表无限小和无限大。
- 返回指定分数范围元素个数
zcount key min max
- 删除指定排名内的升序元素
zremrangebyrank key start stop
- 删除指定分数范围元素
zremrangebyscore key min max
2.集合间操作
- 交集
zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
zinterstore 命令参数比较多:
- destination:将交集的计算结果,保存到这个键中。
- numkeys:需要做交集计算键的个数。
- key [key ...]:需要做交集计算的键。
- WEIGHTS weight:每个键的权重,在做交集计算时,每个键中的分数值都会乘以这个权重,默认每个键的权重为 1。
- AGGREGATE SUM|MIN|MAX:计算成员交集后,分值可以按照 sum(和)、min(最小值)、max(最大值)做汇总,默认值为 sum。
下面我们将权重设置为 0.5,这样当计算交集后,有序集合中的元素分数将都会减半,并且使用 max 参数汇总。
- 并集
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
zunionstore 命令的相关参数和 zinterstore 命令相同。
内部编码
有序集合类型的内部编码有两种,它们分别是:
- ziplist(压缩列表):当有序集合的元素个数小于 128 个(默认设置),同时每个元素的值都小于 64 字节(默认设置),Redis 会采用 ziplist 作为有序集合的内部实现。
- skiplist(跳跃表):当上述条件不满足时,Redis 会采用 skiplist 作为内部编码。
备注:上述中的默认值,也可以通过以下参数设置:zset-max-ziplist-entries 和 zset-max-ziplist-value。
下面我们用以下示例来验证上述结论。
当元素个数比较少,并且每个元素也比较小时,内部编码为 ziplist:
当元素个数超过 128 时,内部编码为 skiplist。下面我们将采用 python 动态创建128个元素,下面为源码:
import redis r = redis.Redis(host='127.0.0.1', port=6379) if r.object('encoding', 'zsetkey') != None: print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8')) for i in range(1, 600): r.zadd('zsetkey',i,1) if r.object('encoding', 'zsetkey') != None: print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8')) Key为【zsetkey】的字节编码为【ziplist】 Key为【zsetkey】的字节编码为【skiplist】
当有序集合中有任何一个元素大于 64 个字节时,内部编码为 skiplist。
import redis r = redis.Redis(host='127.0.0.1', port=6379) if r.object('encoding', 'zsetkey') != None: print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8')) value = '' for i in range(1, 600): value += str(i) r.zadd('zsetkey',value,1) if r.object('encoding', 'zsetkey') != None: print('Key为【zsetkey】的字节编码为【%s】' % r.object('encoding', 'zsetkey').decode('utf-8')) Key为【zsetkey】的字节编码为【skiplist】