redis里的zset是一种有序集合,从逻辑上,可以理解为在集合的基础上,为每一个成员增加了分数字段,分数是一种浮点数值,并可以相同,它们按序排列起来。它能对分数在增,删,查找上都能提供对数时间复杂度的操作。
redis里的zset是利用一个skiplist和一个dict实现的,其中关键数据结构就是skiplist,跳跃表。
skiplist的原理和基本实现网上有很多,不再啰嗦。基于zset的需求,会有稍微的改进。
1,结构定义
typedef struct skiplistnode {
double score;
int member; /* (1) */
struct skiplistnode *backward; /* (2) */
struct skiplistforward {
struct skiplistnode *ptr;
unsigned long span; /* (3) */
} forward[];
} skiplistnode;
typedef struct skiplist {
int level;
unsigned long length;
struct skiplistnode *header, *tail;
} skiplist;
基本参考了redis的定义,其中member为什么是int(1),是用来放luaL_ref的ref值,所有放在容器里的数据都会先引用,然后记在这个C结构里,所以它可以记任何lua类型:函数,对象等。那个backward(2),是用反向遍历结点,比如zrevrange*这类操作。那个span的含义是(3),当前那一层的下一跳ptr距离本结构的距离,这是为了实现rank计算排名等相关操作的,为什么要这样定义,这样可以在只有局部结点变化时,需要修改的span值比较少,而且也比较容易实现各种计数操作。
2,为什么skiplist有平均很好的性能
具体有严格的证明,我只写下我自己的理解。本质上它就是一个list,只不过每个结点有一个层级level,任何结构的层级都是随机生成,像这样:
int
sl_randomlevel() {
int level = 1;
while (random() * 2 < RAND_MAX)
level++;
return level > MAX_SKIP_LEVEL ? M