ZSET
有序集合,这里的有序指的是升序/降序,相当于顺序是固定的
zset中的member还是要求是唯一的
ZADD
向集合中添加新的元素
操作格式:
zadd key NX \| XX GT \| LT CH INCRscore member score member...
score:因为是要保证有序,score就是进行排序的条件
member:添加的成员
NX:与前面的含义一样,只有member不存在的时候才可以添加
XX:只有member存在的时候才可以添加
GT:添加新的元素是,判定元素score要大于当前最大的score才可以更新
LT:添加新的元素是,判定元素score要小于当前最小的score才可以更新
CH:正常来讲ZADD返回值应该是新增个数,添加完CH选项,返回值除新增个数加上被修改的个数
INCR:此时命令类似ZINCRBY的效果,将元素增加上指定的score,此时只能指定一个元素和score
返回值:
新增成功的元素个数,如果没进行添加新元素,而是只进行修改的话,返回值为0
时间复杂度:
O(logN):N为有序集合中的元素个数,因为要保证有序性,所以需要找到添加位置,可以想象成二分查找,但并不是二分查找,内部实现是使用了跳表,二者结构并不相似,我的意思是二者都利用了有序性实现快速查找,致使其效率达到logN而不是N
如果score(判定条件)是一样的怎么进行排序?
如果score是一样的,那么排序就会按照字典序(按照member的字典序)进行排序
ZSET默认是按照升序进行排序的
举个例子:
cpp
zadd key1 incr 4 张飞
ZRANGE
查看有序集合中的元素详情,有序集合,本身元素就是有先后顺序的,所以就有下标的概念
操作格式:
zrange key start end withscores
withscores:添加该选项可以查看score,也就是可以查看为什么这么排序,没有这个选项的话,只会给出结果,并不会显示根据什么排序得到这个结果的
举个例子:
cpp
zrange key1 0 -1
ZCARD
获取zset中元素个数
返回值:
zset中的元素个数
时间复杂度:
O(1)
操作格式:
zcard key
ZCOUNT
返回分数在min和max之间的元素个数,默认情况下,min和max是都包含的,可以通过()进行排除
返回值:
满足条件的元素个数
时间复杂度:
O(logN)
现根据min找到对应的元素,在根据max找到对应的元素,因为在每个元素上还记录了该元素处于当前集合中的排名/下标,所以找到max和min,二者一减就可以得到结果,并不是通过遍历得到的!!!
操作格式:
zcount key min max
注意:
min和max是可以写成浮点数,因为zset本身就可以将score设置成浮点数
在浮点数中,存在两个特殊的值:
inf:无穷大
-inf:负无穷大,负无穷大不是无穷小,无穷小是无限接近于0的值,负无穷大与正无穷大的绝对值是一样的
通过这个命令也可以看出来,这里的member socre并不是键值对,而是普通的std::pair<>,因为要是键值对key,val的话只能通过key去查val,而这里是双向得到既可以通过member找到score,也能通过score找到member
举个例子:
cpp
//查找key中不包含95而包含97的member
zcount key (95 97
//查找key中不包含95也不包含97的member
zcount key (95 (97
//不包含哪边,就在哪边加上(,可以理解成()表示开区间
ZRANGE
返回指定区间的元素,按照score升序排序
操作格式:
zrange key start stop withscores
withscores:不光显示找到的元素,还显示根据什么进行的排序,也就是score
时间复杂度:
O(log(N) + M)
现根据start找到对应的元素,在根据end找到对应的元素,这时O(log(N))的由来,M则是遍历处于该范围内的元素所产生的时间
返回值:
区间内的元素列表
ZREVRANGE
返回指定区间内的元素,score按照降序进行排序
操作格式:
zrevrange key start stop withscores
时间复杂度:
O(log(N) + M)
现根据start找到对应的元素,在根据end找到对应的元素,这时O(log(N))的由来,M则是遍历处于该范围内的元素所产生的时间
返回值:
区间内的元素列表
举个例子:
cpp
zrevrange key 0 -1 withscores
ZRANGEBYSCORE
返回在min和max之间的元素,默认情况下,min和max都是包含的,不想包含的话可以通过"("进行排除
返回值:
区间内的元素列表
时间复杂度:
O(log(N) + M)
操作格式:
zrangebyscore key min max witgscores
注意:
与ZRANGE的区别就是,ZRANGE按照下标找元素的,而ZRANGEBYSCORE按照score找元素的
这个命令可能在6.2.0之后废弃,因为将该功能合并到ZRANGE中
ZPOPMAX
删除并返回最高的count元素
操作格式:
zpopmax key count
返回值:
socre和member列表
时间复杂度:
O(log(N) * M),log(N),是找到目标元素所花的时间,M是count个数
如果存在多个元素,socre(分数)相同,同时为最大值,zpopmax删的时候,仍然只删除其中一个元素,删除规则按照字典序删除的
BZPOPMAX
删除最大值的阻塞版本
操作格式:
bzpopmax key key... timeout
timeout:表示要阻塞多久,单位是秒,支持写成小时形式
时间复杂度:
O(log(N)),log(N)找到最大值所花的时间
返回值:
返回值分为三部分,第一,标识了从哪个集合中拿取的,第二,标识了member,第三标识了scorce
注意:
这里的有序集合,可以视为优先级队列,所以有的时候也需要一个带有阻塞功能的优先级队列
阻塞也是在有序集合为空的时候出发阻塞,阻塞到有其他客户端插入元素
如果集合中有元素了,则不会触发阻塞
ZPOPMIN
删除并返回最高的count元素
操作格式:
zpopmin key count
返回值:
socre和member列表
时间复杂度:
O(log(N) * M),log(N),是找到目标元素所花的时间,M是count个数
如果存在多个元素,socre(分数)相同,同时为最大值,zpopmax删的时候,仍然只删除其中一个元素,删除规则按照字典序删除的
BZPOPMIN
删除最大值的阻塞版本
操作格式:
bzpopmin key key... timeout
timeout:表示要阻塞多久,单位是秒,支持写成小时形式
时间复杂度:
O(log(N)),log(N)找到最小值所花的时间
返回值:
返回值分为三部分,第一,标识了从哪个集合中拿取的,第二,标识了member,第三标识了scorce
注意:
这里的有序集合,可以视为优先级队列,所以有的时候也需要一个带有阻塞功能的优先级队列
阻塞也是在有序集合为空的时候出发阻塞,阻塞到有其他客户端插入元素
如果集合中有元素了,则不会触发阻塞
ZRANK
返回指定元素的排名,按升序进行计算的
操作格式:
zrank key member
时间复杂度:
O(log(N)),log(N)找到该成员所花的时间
返回值:
该member的排名
ZREVERANK
返回指定元素的排名,按降序进行计算的
操作格式:
zreverank key member
时间复杂度:
O(log(N)),log(N)找到该成员所花的时间
返回值:
该member的排名
ZSCORE
返回指定元素的score
操作格式:
zscore key member
返回值:
分数
时间复杂度:
O(1)
为什么这里不是O(logN)?
因为该操作比较高频,此处相对于redis对于这样的查询操作做了特殊优化,付出了额外的空间代价
ZREM
删除指定元素
操作格式:
zrem key member member...
返回值:
本次操作删除的元素的个数
时间复杂度:
O(M*logN),M为具体删除个数,logN为每次定位元素所花的时间
ZREMRANGEBYRANK
按照升序,删除指定范围的元素,左闭右闭
操作格式
zremrangebyrank key start stop
start,stop:为下标,两个下标表示一段范围
返回值:
本次操作删除的元素的个数
时间复杂度:
O(M + logN),M为具体删除个数,logN为每次定位元素所花的时间,也就是定位开头和结尾,花费M的时间进行遍历范围内的值
举个例子:
cpp
zremrangebyrank key1 0 -1
ZREMRANGEBYSCORE
按照分数指定的范围的元素进行删除,左闭右闭
操作格式:
zremrangebyscore key min max
返回值:
本次操作删除的元素的个数
时间复杂度:
O(M + logN),M为具体删除个数,logN为每次定位元素所花的时间,也就是定位开头和结尾,花费M的时间进行遍历范围内的值
ZINCRBY
为指定的元素的关联分数添加/减去指定分数值
操作格式:
zincrby key increment member
increment:增加/减少多少score(分数),该值可以是小数和负数
返回值:
增加/减少后元素的score(分数)
时间复杂度:
O( logN)
注意:
增加可以增加负值,可以当作减法去使用
不光会修改分数内容,也能同时移动元素位置,保持整个有序集合仍是升序的
举个例子:
cpp
zincrby key1 -10.5 member
ZINTERSTORE
找到多个key的交集
操作格式
zinterstore destination numkeys key key... weights weight \[weight...] aggregate \
destination:要把交集结果存储到哪个zset有序集合中
numkeys:表示有多少个key
为什么要添加numkeys 这个参数?
因为该命令后面存在大量选项,使用该参数可以将key有后面选项进行分离,类似协议中将报头与有效载荷分离的操作,要不然无法区分哪几个是key哪些是选项
weights:权重,有序集合是带有分数的,此处指定的权重,相当于一个系数,添加该选项的话,那么结果的分数则是当前分数乘以权重,为该集合的该member成员的分数,再根据aggregate所选择处理集合和集合之间source方式,来得到最终结果
注意:
权重的数值可以是小数,相当于百分比
[aggregate <sum | min | max>:经过权重计算完后,那只得到了集合的成员所对应的分数,但是此处求得是交集,前置知识,相同member就是满足交集的要求,分数可以不同,所以作为交集该最新结果的分数因该根据该选项进行计算,sum是让多个相同member的socre结果进行相加,该方法也是默认方法,min是多个socre选最小的,max是多个socre选最大的


时间复杂度:
O(N * K) + O(M * log(M))
N:输入若干个有序集合,里面元素最小的个数
K:集合的个数
M:最终结果的有序集合元素个数
ZUNIONSTORE
求多个有序集合之间的并集
操作格式:
zunionstore destination numkeys key key... weights weight \[weight...] aggregate \
用法与zuinion几乎一致
时间复杂度:
O(N) + O(M * logM)
N:表示总的元素个数
M:表示总的结果集的个数
举个例子:

ZSET编码方式
ziplist:压缩列表
skiplist:跳表,简单来说就是一个复杂的"链表"
跳表的时间复杂度:O(logN)
ZSET应用场景
可以做各种排行榜,核心就是使用有序的特性