一、set类型的基本介绍
谈到一个术语,这个术语很可能有多种含义。Set一个含义是集合,一个含义是设置。
集合就是把一些有关联数据放到一起。
1、集合中的元素是无序的!
2、集合中的元素是不能重复的。
和list类似,集合中的每个元素,也都是string类型,也可以使用,json这样的格式让string也能存储结构化的数据。
set的命令
SADD将一个或者多个元素添加成员。咱们把集合中的元素叫做member。
集合是不能重复的。
返回值表示本次操作,成功添加了几个元素
SMEMBERS 获取集合中的所有元素 以上两个命令的时间复杂度都是O(1)的。
SISMEMBER 判断一个元素是否在集合里
返回值1和0表示存在和不存在。
SPOP POP一般表示从末尾删除一个元素。集合中的元素是无序的,此时哪个元素是末尾。使用spop删除元素的时候其实是随机删除
count不写的时候,随机删除一个。写的时候,就是写几个就删几个。
官方文档承诺了咱们是随机的。
SRANDMEMBER,只是拿一个元素,而不会删除。其实在源码中,针对spop实现的时候,就采取了生成随机数的方式。
SMOVE 将一个元素从源集合放到另一个集合中。
SREM 可以一次删除一个member,也可以一次删除多个member
返回值表示删除成功的元素个数。
集合间操作
SINTER,求交集。每个key都对应一个集合。返回值就是交集的数据。
O(N*M) N是最小的集合元素个数,M是最大的集合元素个数。
sinterstore直接把算好的交集放到destination这个key对应的集合当中去了,这里的返回值就是交集的元素个数。要想知道交集的内容,直接按照集合的方式访问destination这个集合的可以即可。
SUNION 求并集。时间复杂度O(N)N值是总的元素的个数。
SUNIONSTORE
SDIFF 求差集。时间复杂度O(N)N值是总的元素的个数。
SDIFFSTORE
以上四个命令和上面的交集运算用法是相同的。返回值也相同。
set命令小结
set编码方式
intset整数集合:为了节省空间,做出的特定优化。当元素均为整数,并且元素个数不是很多的时候,使用它就是为了节省空间,因为是内存数据库。
hashtable哈希表,不是整数的化就用hash表来存储。
set类型应用场景:
使用set里保存用户的标签
用户画像,分析出你这个人的一些特征,分析清楚特征之后,再投其所好。窥探用户隐私。这种东西避免不了。特征:性别,年龄,居住地,爱好。不同的用户,商业价值是不同的 --- 是否愿意花钱。 女人 》 孩子 》 老人 》 狗 》 男人。这些特征根据用户的一些历史行为,就能看出来。上述用户数据,很多公司之间在共享。两个程序,两个账号,咋知道这两账号是一个人呢?现在的程序登录,主要就是两入口,手机号和微信。通过上述过程搜集到的用户特征,就会转换为标签。
标签就是简短的字符串,此时就可以把标签保存到redis的set中了。用户画像这种事情其实是挺复杂的事情,一般一个大厂都会有专门的团队做这样的工作。
上述玩法,抖音玩的是最好的,其他互联网大厂一看这么搞真好,都纷纷跟进。但是,我们看到的内容始终就是一个小圈子。所以我们需要有一个开放眼界的过程。
Set方便计算交集,很容易的找到两个用户之间的公共标签,基于这样的标签,衍生出一些用户关系。
使用Set来计算用户之间的共同好友
基于集合求交集的操作。就比如QQ就有这样的功能。
使用Set统计UV
去重,一个互联网产品,如何衡量用户量,用户规模??
主要的指标是两方面:
1、PV page view。用户每次访问该服务器,每次访问都会产生一个PV。
2、UV user view 每个用户,访问服务器,都会产生一个UV,但是同一个用户多次访问,不会使UV增加。uv需要按照用户进行去重,上述的去重过程,就可以使用set来实现。
二、zset有序集合
set集合,唯一,无序。
List有序的。
zset有序集合:升序/降序。排序的规则是啥?给zset中的member同时引入了一个属性,分数score,浮点类型。每个member都会安排一个分数,进行排序的时候,就是依照此处的分数大小来进行升序/降序排序。
zset中的member仍然要求是唯一的。score则可以重复。zset主要还是用来存member的。score只是辅助。
zset类型的操作命令
ZADD往有序集合中,添加member和score
score member:添加的时候既要添加元素又要添加分数,member和score称为是一个pair,这个类似于C++里谈到的std::pair。不要把member和score理解成键值对。键值对中,是由明确角色区分,谁是键,谁是值,是明确的。一定是根据键 -》值。
对于有序集合来说,是既可以通过member找到对应的score,又可以通过score找到匹配member。
XX 只更新已经成功的member
NX只添加新的member。
不加选项:如果当前member不存在,此时就会达到添加新member的效果。如果当前member已经存在,此时就会更新分数。
LT 只更新已经存在的分数,现在要更新分数,发现现在给定的新的分数,比以前的分数小,此时就跟新成功,否则就不成功
GT 现在要更新分数,发现现在给定的新的分数,比以前的分数大,此时就跟新成功,否则就不成功
CH 描述了返回值返回什么样的信息。本来zadd返回的是新增的元素个数,添加CH还会返回被修改的元素个数。影响的是zadd的返回值。
INCR ZADD相当于ZINCRBY。自增。
时间复杂度为O(logN)N的值为元素的个数。由于zset是有序结构,要求新增的元素,要放到合适的位置上。当然之所以是logN不是N,也是充分的利用了有序这样的特点,当然zset内部的数据结构,主要是跳表。如果分数相同,再按照元素自身字符串的字典序来排列。实际上zset内部就是按照升序方式来排列的。
ZRANGE查看有序集合中的元素详情。有序集合本身元素就是先后顺序的。谁在前,谁在后,都是很明确的。因此也就可以给这个有序集合赋予下标这样的概念了。
如果修改的分数,影响到了之前的顺序,就会自动的移动元素,保持原来的有序。
ZCARD获取元素个数。
ZCARD key。
ZCOUNT返回分数在min和max之间的元素个数。
默认是一个闭区间。如果想排除边界值,可以加上括号。
zcount的时间复杂度O(logN)。先根据min找到对应的元素,再根据max找到对应的元素。如果区间中的元素比较多,此时要进行遍历,复杂度就成了O(logN + M),zset实际上没有这么做,它的内部会记录每个元素当前的排行/次序。查询到元素,就直接知道了元素所在的次序,就可以直接把max对应的元素次序和min对应的元素次序,减法即可。min和max是可以浮点数,在浮点数中存在两个特殊的数值:inf无穷大,-inf负无穷大。zset中分数也是支持使用inf和-inf做为max和min的。
ZRANGE 返回指定区间里的元素时间复杂度为O(log(N) + M) 找到start对应的位置,接下来就需要遍历了,M就是start到stop区间的元素个数。
ZREVRANGE 按照分数降序遍历并打印
ZRANGEBYSCORE,按照分数来找元素,不是下标相当于和zcount相似
时间复杂度为O(log(N) + M)。这个命令可能在6.2.0之后废弃,并且功能合并到ZRANGE中。
ZPOPMAX 删除并返回分数最高的count个元素。topk问题。
返回值就是被删除的元素。如果存在多个元素,分数相同,同时为最大值,zpopmax删除的时候,仍然只删除其中一个元素!根据字典序来决定先后。O(log(N)*M) N是有序集合的元素个数,count要删除的元素个数。此处删除的是最大值,有序集合,最大值就相当于最后一个元素(尾删)。既然是尾删,为什么我们不把这个最后一个元素的位置特殊记录下来,后序删除不久可以O(1)了嘛,省去了查找的过程。这个事情是有可能的。但是很遗憾redis没有这么做。事实上redis源码中,针对有序集合,确实是记录了尾部,这样的特定位置。但是在实际删除的时候,并没有用上这个特性,而是直接调用了一个通用的删除函数。给定一个member的值,进行查找找到位置之后再删除。
BZPOPMAX ZPOPMAX的阻塞版本
咱们这里的有序集合也可以视为是一个优先级队列,有的时候,也需要一个带有阻塞功能的优先级队列。timeout 表示超时时间,支持小数,单位是s。
删除最大值,花的时间
O(log(N))。啥时候*M每个这样的key上面都删除一次元素。BZPOPMAX是从这若干个key中只删除就绪的key一次。
ZPOPMIN
BZPOPMIN 这两个命令和上述的命令相对。时间复杂度和上述的命令相同。
ZRANK返回指定元素的排名,意思就是下标
时间复杂度O(logN)。最主要是有一个查询位置的过程。zrank得到的下标是从前往后算的。
ZREVRANK key member 也是获取到member的下标,但是是反着算的。
ZSCORE 查询指定member的分数,时间复杂度O(1)。前面根据member找元素,都是LogN,这里也是先找元素啊。此处相当于redis对于这样的查询操作,做了特殊优化,付出了额外的代价,针对这里进行了优化到O(1)的实现。
ZREM删除指定key的元素 ,时间复杂度O(logN*M),
返回值表示删除元素的个数。
ZREMRANGEBYRANK
,使用这个下标描述的范围进行删除。O(logN+M)。N整个有序集合的元素个数,M是start到stop区间中元素个数。此处查找的位置,只需要进行一次。区间是闭区间。
ZREMRANGEBYSCORE
指定一个删除的区间,通过分数来描述的。闭区间。小括号排除边界值。
时间复杂度也是O(log(N) + M)
ZINCRBY 自增操作
返回值为增加后的分数,不光会修改分数内容,也能同时移动元素位置,保持整个有序集合仍然是升序的。
ZINTERSTORE 求交集放到另一个有序集合中。
destnation 要把结果存储到哪个key,对应的zset中,
numkeys 整数,描述了后续有几个key参与交集运算。主要是因为,numkeys描述出key的个数之后,就可以明确的知道,后面的选项是从哪里开始了,避免选项和keys混淆。此处的设定,特别像之前学过的两个知识点,本质上是一个,HTTP协议
首行
请求头 Content-Length 描述了正文的长度!如果这个东西没有,这里数据错了,就容易产生粘包问题,HTTP在传输层,是基于TCP,TCP是面向字节流的!粘包问题,是面向字节流这种IO方式普遍存在的问题。文件读写也是面向字节流的。描述包的长度和边界。
空行
正文
WEIGHTS 权重综合考虑。咱们的集合是有序集合!带有分数,此处指定的权重相当于一个系数,会乘以当前的分数。
AGGREGATE 总数,有序集合中member才是元素的本体,score只是辅助排序的工具。因此在进行比较相同的时候,只要member相同即可,score不一样,如果member的分数不同,进行交集合并之后的最终分数,咋算?SUM | MIN | MAX 三个选项。和,取最小,取最大。
时间复杂度为
化简以下得到一个近似值。K一般来说都不会很多,近似看作1。化简以下认为N和M是接近的。同一个数量级。O(M) + O(M*logM) =》O(M*logM)上述事件复杂度都不需要我们记,面试也不会问这个,需要考察你解决问题的能力。
ZUNIONSTORE 求并集放到另一个有序集合中,这个命令和上一个命令相同
zset命令小结:
zset编码方式:
如果有序集合中的元素个数较少,或者单个元素体积较小,使用ziplist来存储,如果当前元素个数比较多,或者单个元素体积非常大,使用skiplist来存储了。都有对应的配置项。
关于跳表:
简单来说,跳表是一个复杂链表。查询元素,时间复杂度为logN。相比于树形结构,更适合按照范围获取元素。B+树。
zset的应用场景:
最关键的应用场景,排行榜系统。关键要点用来排行的分数是实时变化的。,虽然是实时变化,也能够高效的更新排行。
微博热搜、游戏天梯排行,成绩排行。