zset 可能是Redis提供的最有特色的数据结构,它也是在面试中面试最爱问的数据结构,如图所示,它类似Java的SortedSet和HashMap的结合体,一方面它是一个set,保证了内部value的唯一性,另一个方面它可以给每个value赋予一个score,代表这个value的排序权重。它的内部实现用的是一种跳跃列表的数据结构。
zset中最后一个value被移除后,数据结构被自动删除,内存被回收。
zset可以用来存储粉丝列表,value值是粉丝的用户id,score 是关注时间,我们可以对粉丝列表按关注时间进行排序。
zset还可以用来存储学生的成绩,value值是学生的id,score 是他的考试成绩,我们对成绩按分数进行排序就可以得到他的名次。
【跳跃列表】
zset内部的排序功能是通过“跳跃列表”数据结构来实现的,它的结构非常特殊,也比较复杂。
因为zset 要支持随机的插入和删除,所以它不宜使用数组来表示,我们先看一个普通的链表数据结构,如图下:
我们需要这个链表按照score值进行排序,这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可以继续保证链表是有序的,通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到,那该怎么办呢?
假设有一家创业公司,刚开始只有几个人,团队成员之间人人平等,都是联合创始人。随着公司的成长,人数渐渐变多,团队沟通成本随之增加,这时候就会引入组长制,对团队进行划分,每个团队会有一个组长,开会的时候分团队进行,多个组长之间还会有自己的会议安排。当公司规模进一步扩展,需要再增加一个层级-----部门,每个部门会从组长列表中推选出一个代表作为组长。部长们之间还会有自己的高层会议安排。
跳跃列表就类似于这种层机制,最下面一层所有的元素都会串起来,然后每隔几个元素挑选出一个代表,再将这几个代表使用另一级指针串起来,然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构。
想想你老家在世界地图中的位置:亚洲->中国->某省->某市->某县->某镇->某村->门牌号,也是这样一个结构。
跳跃列表之所以跳跃,是因为内部的元素可能身兼数职,如下图,中间的这个元素,同时处于L0,L1和L2层中,可以快速在不同层次之间进行跳跃。
定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插入进去,你也许会问,那新元素如何才有机会身兼数职呢?
跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。
首先其位于L0层的概率肯定是100%,而兼职到L1层只有50%的概率,到L2层只有25%的概率,到L3层只有12.5%的概率,以此类推,一直随机到最顶层L31层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层,列表中的元素越多,能够深入的层次越深,元素能进入到顶层的可能性会越大。