Redis数据结构之zset
一.zset有序集合
它和集合唯一不同的就是,有序集合中的每一个元素都有一个唯一对应的浮点类型的分数与之关联着,是的有序集合中的元素可以维护有序性。
但是这个有序不适用下标作为排序的依据,而是使用这个分数。就好像排行榜一样,谁得到的分数多,就排在前面
所以这里的有序和list中的有序的概念不一样,这里强调升序/降序
注意zset中的member还是不能够重复,但是分数可以重复
二.相关命令
1.zadd
由于zset是有序的,所以要找位置。当然之所以不是O(N),也是利用了有序的特点,zset的内部数据结构主要是跳表
2.zrange
zrange key start stop (withscores)
如上,zset就是一个升序的集合,返回值也是按照升序排列的
3.zcard
返回集合中的元素个数
4.zcount
zcount key minscore maxscore
返回minmax之间的元素个数,闭区间
如果像去掉边界值,就可以加上括号
比如不想要左边的边界:zcount z1 (90 96
如果右边的边界也不想要:zcount z1 (90 (96 注意,是左边加括号!!!!!
时间复杂度O(logN)先根据min找到对应的元素,再根据max找到对应的元素,然后进行一个区间遍历,就能知道元素个数了,所以就是O(logN+M)
实际上,zset集合内部,会记录每一个元素的当前排行/次序,查询到元素,就直接知道了元素所在的次序,就可以直接把max和min对应的元素下标相减,就能得到元素个数,所以直接就是O(logN)
min max可以写成浮点数,因为score本身就是浮点数
在浮点数中,有俩个特殊值:inf和-inf,zset可支持inf和-inf作为max和min
5.zrevrange
zrevrange key start stop withscores
reverse逆序,将升序改成降序
6.zrangebyscore
zrangebyscore key min max [withscores] 按照分数找元素,和刚才的zcount类似,不过这个是把元素都罗列出来
时间复杂度:O(logN+M)
7.zpopmax
删除分数最好的元素,返回值是被删除的元素加分数
如果存在多个元素分数相同,就会按照字典序来删
这个命令的时间复杂度是O(logN)
此处删除最大值,就相当于是尾删,既然是尾删,那为啥不把最后一个元素的位置记录下来,使得时间复杂度变成O(1)呢?其实是可以的,但是redis没有采取这个做法,而是直接调用了通用的删除函数
8.bzpopmax
这是阻塞版本的。咱们这里的有序集合,也可以视为优先级队列,有时也需要一个带有阻塞功能的优先级队列。
和list中的blpop brpop用法一样
如上,返回值就是集合名字+member+分数
时间复杂度:O(logN)
如果此时bzpopmax同时监听了多个key,假设是M个,那时间复杂度会不会是M*logN?不是,因为它是删除最先执行命令的客户端的key
9.zpopmin bzpopmin
和max的用法一致
10.zrank zrevrank
zrank/zrevrank key member 得到member的排名
时间复杂度O(N),最主要是有个查询位置的过程
这俩个的区别就是,一个是从前往后遍历(升序),一个是从后往前遍历
11.zrem
一次删除一个或多个成员
时间复杂度:O(M+logN) N是有序集合中元素都个数,M是stop-start的元素个数
12.zremrangebyscore
zremrangebyscore key min max
删除指定分数范围内的元素,时间复杂度:O(M+logN)
同样,默认是闭区间,如果想让他是开区间那就加上左括号!!!
13.zincrby
zincrby key increment member 给元素加分数
14.zinter zinterstore
zinter是从redis6.0开始支持的,这里先不讲
先看zinterstore
zinterstore destination numkeys key [key……] [weights weight [weight……]]
destination描述了将交集放到哪个集合,numkeys描述了后面几个集合在求交集,weights后面是每一个集合的权重/占比(就是相当于一个系数,会乘以当前的分数)
如上,就是说z1占2份,z2占3份,分数相乘再相加即可
时间复杂度:O(N*K)+O(M*log(M)) N 是输⼊的有序集合中, 最⼩的有序集合的元素个数; K 是输⼊了⼏个有序集合; M 是最终结果的有序集合的元素个数 N和M很相近,K一般很小可以看成1,所以化简一下就是O(M*logM)
15.zunion zunionstore
和zinterstore用法一致
三.内部编码
1.ziplist:如果内部元素个数少,或者单个元素体积小,就是用ziplist存储。指标是zset-max-ziplist-entries和zset-max-ziplist-value
2.skiplist:如果元素个数多,或者单个元素体积大,就是用跳表。跳表是一个复杂的链表。
四.应用场景
1.排行榜系统
微博热榜,游戏天梯排行,成绩排行……
关键是,用来排行的分数是不断变化的,虽然是实时变化的,但也能够高效率的更新排行
游戏排行榜算是简单的引用,还有一些排行榜就有一点复杂,比如微博热度:1.浏览量2.点赞量3.转发量4.评论量…… 根据每一个维度计算到综合得分,就是微博的热度。此时就可以借助zinterstore/zunionstore来按照加权的方式处理了。