redis吸引很多人使用的一个重要的原因,就是它对众多数据类型的支持。包括string,hash,set,zset,list等五种数据对象。
其中zset用来保证数据的有序存储,实现中,redis使用跳跃表和压缩列表,作为zset的底层实现。当元素数量比较多,或者元素成员是比较长的字符串时,底层实现采用跳跃表。
跳跃表是什么?
一种有序的数据结构,通过在节点中维持多个指向其它节点的指针,达到快速访问的目的。
跳跃表的好处是什么?
1. 跟平衡树相比,实现简单;
2. 平均复杂度为O(logN),最坏为O(N);
跳跃表的数据结构由 zskiplistNode 和 zskiplist 两个结构定义,zskiplistNode表示跳跃表节点,zskiplist保存跳跃表节点的相关信息。
zskiplistNode的数据结构如下:
typedef struct zskiplistNode {
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode
skiplistLevel包含多个元素,每个元素包含一个指向其它节点的前进指针,通过它来加快访问其它节点。每次创建一个跳跃表节点的时候,会随机生成一个值,该值介于1和32之间,作为level数组的大小。span表示层的跨度,用来计算最终的位置,程序在遍历时,会将沿途访问过的所有层的跨度累计,得出目标节点的对应位置;
backward用于从表尾向表头访问节点;
score 所有节点都按照它进行排序;
obj 指向一个字符串对象;
zskiplist的数据结构:
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
header,tail用来表示跳跃表的表头和表尾巴节点,length表示跳跃表的整体长度,level表示跳跃表的高度。
zskiplist的主要作用,是可以方便的对整个跳跃表进行处理,比如获取跳跃表的整个长度的信息。
跳跃表的层数如何生成?
在插入的过程中构造,向跳跃表中插入一个数值,相当于在表中插入一列从起始跳跃表节点S出发,向上的一段数值,需要确定两个元素:数值的位置和层数。由于所有链是递增序列的方式,所以位置主要是根据对应数值的比较对出。而层数,程序会先记录比该节点小的值的span,然后,随机生成介于1和32之间的数值,作为该节点的层数,该算法主要是基于冥次定律,越大的数出现的概率越小。假设level为2的概率为P,则level为3的概率为p*p,以此类推。
跳跃表查找的示例图:
图中,表示有三个节点,他们对应的score分别为1,2,3,其中,虚线,代表在跳跃表中,查找score为2,成员对象为02的节点,在查找过程中,经过了两个span为1的节点,所以,可以得出该节点在跳跃表中的排位为2。 BW表示后退指针,实线的,表示节点间的span,由于节点间的关系比较多,所以只画了几个。