在计算机科学领域,跳跃链表是一种数据结构,允许快速查询一个有序连续元素的数据链表。快速查询是通过维护一个多层次的链表,且每一层链表中的元素是前一层链表元素的子集。一开始时,算法在最稀疏的层次进行搜索,直至需要查找的元素在该层两个相邻的元素中间。这时,算法将跳转到下一个层次,重复刚才的搜索,直到找到需要查找的元素为止。-------维基百科
简单的来说,跳跃表是一个有序的数据集合,里面每个跳跃节点存贮着大量指向其它跳跃节点的指针,来实现快速访问其它节点的目的。它的性能可以媲美平衡树,且比平衡树更容易实现。如图所示(图片来自维基百科):
而在Redis中,跳跃表是作为有序集合的底层实现之一,当有序集合含有的数据比较多,又或者数据是比较长的字符串时,Redis就会采用跳跃表来实现有序集合。
定义
typedef struct zskiplistNode {robj *obj;double score;struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned int span;} level[];
} zskiplistNode;
obj指向的是一个sds,而level中存贮着跳向另外节点的指针与它们之间的跨度,跨度指的是从该节点到目标节点跨过的多少节点。 无目标节点则为0,有目标节点从1开始计算。且跨度是用来计算该节点的排位值的,在遍历时该节点经历过的节点的跨度和即为该节点的排位值。
typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;
创建
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));zn->score = score;zn->obj = obj;return zn;
}
#define ZSKIPLIST_MAXLEVEL 32
zskiplist *zslCreate(void) {int j;zskiplist *zsl;zsl &#61; zmalloc(sizeof(*zsl));zsl->level &#61; 1;zsl->length &#61; 0;zsl->header &#61; zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);for (j &#61; 0; j < ZSKIPLIST_MAXLEVEL; j&#43;&#43;) {zsl->header->level[j].forward &#61; NULL;zsl->header->level[j].span &#61; 0;}zsl->header->backward &#61; NULL;zsl->tail &#61; NULL;return zsl;
}
需注意的是&#xff0c;在Redis中节点的层次大小是随机的&#xff0c;是采用了幂次定律&#xff0c;即越大的数几率越小&#xff0c;它会产生一个1至32的数。
#define ZSKIPLIST_P 0.25
int zslRandomLevel(void) {int level &#61; 1;while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))level &#43;&#61; 1;return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
释放
void zslFreeNode(zskiplistNode *node) {decrRefCount(node->obj);zfree(node);
}
void zslFree(zskiplist *zsl) {zskiplistNode *node &#61; zsl->header->level[0].forward, *next;zfree(zsl->header);while(node) {next &#61; node->level[0].forward;zslFreeNode(node);node &#61; next;}zfree(zsl);
}
值得注意的是释放节点函数中的decrRefCount函数&#xff0c;因为Redis自己用c实现了对象与对象的回收机制&#xff08;计数回收&#xff09;&#xff0c;调用这个方法其实就是在调用该对象的数量上减一。
遍历
跳跃表的遍历是采用前进指针来完成的。而采用后退指针可以完成从分值大的到小的一次遍历。遍历的过程如图所示&#xff08;黑色表示前进遍历&#xff0c;红色表示后退遍历&#xff09;&#xff1a;
跳跃表的查找都是每次从每个跳跃节点的最高层次开始查找的&#xff0c;是跳着查找的&#xff0c;查找的过程代码所示&#xff1a;
x &#61; zsl->header;for (i &#61; zsl->level-1; i >&#61; 0; i--) {while (x->level[i].forward &&(x->level[i].forward->score < score ||(x->level[i].forward->score &#61;&#61; score &&compareStringObjects(x->level[i].forward->obj,obj) < 0)))x &#61; x->level[i].forward;}
如图&#xff1a;
插入
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;unsigned int rank[ZSKIPLIST_MAXLEVEL];int i, level;redisAssert(!isnan(score));x &#61; zsl->header;for (i &#61; zsl->level-1; i >&#61; 0; i--) {rank[i] &#61; i &#61;&#61; (zsl->level-1) ? 0 : rank[i&#43;1];while (x->level[i].forward &&(x->level[i].forward->score < score ||(x->level[i].forward->score &#61;&#61; score &&compareStringObjects(x->level[i].forward->obj,obj) < 0))) {rank[i] &#43;&#61; x->level[i].span;x &#61; x->level[i].forward;}update[i] &#61; x;}level &#61; zslRandomLevel();if (level > zsl->level) {for (i &#61; zsl->level; i < level; i&#43;&#43;) {rank[i] &#61; 0;update[i] &#61; zsl->header;update[i]->level[i].span &#61; zsl->length;}zsl->level &#61; level;}x &#61; zslCreateNode(level,score,obj);for (i &#61; 0; i < level; i&#43;&#43;) {x->level[i].forward &#61; update[i]->level[i].forward;update[i]->level[i].forward &#61; x;x->level[i].span &#61; update[i]->level[i].span - (rank[0] - rank[i]);update[i]->level[i].span &#61; (rank[0] - rank[i]) &#43; 1;}for (i &#61; level; i < zsl->level; i&#43;&#43;) {update[i]->level[i].span&#43;&#43;;}x->backward &#61; (update[0] &#61;&#61; zsl->header) ? NULL : update[0];if (x->level[0].forward)x->level[0].forward->backward &#61; x;elsezsl->tail &#61; x;zsl->length&#43;&#43;;return x;
}
这段代码比较的难懂&#xff0c;简单的解释一下&#xff0c;其中有两个关键的数组&#xff0c;一个是update&#xff0c;另外一个是rank。update记载的是与新节点有关联的节点&#xff0c;而rank记载的是与update数组中相对应着的rank值&#xff0c;而且rank[0]表示着新节点的前驱节点的排位值&#xff0c;所以它是计算排位值与span值的关键。
删除
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {int i;for (i &#61; 0; i < zsl->level; i&#43;&#43;) {if (update[i]->level[i].forward &#61;&#61; x) {update[i]->level[i].span &#43;&#61; x->level[i].span - 1;update[i]->level[i].forward &#61; x->level[i].forward;} else {update[i]->level[i].span -&#61; 1;}}if (x->level[0].forward) {x->level[0].forward->backward &#61; x->backward;} else {zsl->tail &#61; x->backward;}while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward &#61;&#61; NULL)zsl->level--;zsl->length--;
}
int zslDelete(zskiplist *zsl, double score, robj *obj) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;int i;x &#61; zsl->header;for (i &#61; zsl->level-1; i >&#61; 0; i--) {while (x->level[i].forward &&(x->level[i].forward->score < score ||(x->level[i].forward->score &#61;&#61; score &&compareStringObjects(x->level[i].forward->obj,obj) < 0)))x &#61; x->level[i].forward;update[i] &#61; x;}x &#61; x->level[0].forward;if (x && score &#61;&#61; x->score && equalStringObjects(x->obj,obj)) {zslDeleteNode(zsl, x, update);zslFreeNode(x);return 1;} else {return 0; }return 0;
}
总结
1. 跳跃表是有序集合的底层实现之一&#xff0c;每个跳跃表节点的高度是1至32之间的随机数。
2. 跳跃表的大小是随机的&#xff0c;采用了幂次定律来生成节点的层次大小。
3. 理论上&#xff0c;节点的层次大小越高&#xff0c;跳跃表的效率就越高。可以插入分值相同但是对象不同的节点