跳跃表是一种对顺序链表的改进,将查找顺序链表的O(N)时间优化为O(logN)时间,空间增加约两倍,有两种常见变体,一种是随机跳跃表,使用随机算法决定每个节点的高度,缺陷有产生随机数的开销;另一种是123确定性跳跃表,除了头和尾,第n层相邻的两个节点之间只有1个,2个或者3个第n-1层的节点,当插入或删除时调整结构;另一种对顺序链表的O(logN)优化是,使用一个指针数组,数组中每个指针指向一个链表节点,从而变成二分查找,得到一个O(logN)的时间界,这种方法需要的空间少,缺陷是在链表增删节点时需要对指针数组进行大量的内存搬移,底层调用C库函数memmove可以防止重叠内存拷贝造成踩内存,但是性能堪忧。
redis的跳跃表使用的是随机跳跃表,代码在server.h和t_zset.c中,server.h提供了跳跃表节点和头的定义,如下
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
zskiplistNode是跳跃表节点,包括一个可变长字符串,一个后向指针backward用于指向本节点在level[0]的上一个节点,level结构中每一层都有一个前向指针,以及一个scan表示跨过几个level[0]元素可以达到本层下一个节点。zskiplist是头,包括头尾指针,长度和层数
t_zsets中提供的api:
zslCreateNode:创建一个跳跃表节点,调用者需要提供明确的level
zslCreate:创建一个zskiplist,其结构中有头尾指针,头指针指向一个32层的节点,32层就是支持的最高层数
zslFreeNode:释放一个跳跃表节点的内存,包含其中的sds
zslFree:销毁一个zskiplist,包含所有节点和头的内存释放,方法是顺序遍历level[0]节点
zslRandomLevel:获取一个随机层数,随机算法依据幂率分布,每层节点数约是下一层的1/4
zslInsert:向zskiplist中插入一个节点
zslDeleteNode:在一个zskiplist中删除一个节点,内部函数,调用者需要提供每个level的删除位置
zslDelete:在一个zskiplist中删除一个节点,外部函数,调用者提供score和sds供查找节点,可选择是否保留节点内存
zslValueGteMin:判断value是否比最小值大
zslValueLteMax:判断value是否比最大值小
zslIsInRange:用score判断zskiplist中是否有一部分在range中
zslFirstInRange:找到一个zskiplist中第一个出现在range中的节点
zslLastInRange:找到一个zskiplist中最后一个出现在range中的节点
zslDeleteRangeByScore:删除zskiplist在score范围中的节点
zslDeleteRangeByLex:删除zskiplist在sds范围中的节点
zslDeleteRangeByRank:删除一段索引范围的节点,索引是level[0]中出现的位置
zslGetRank:查找一个节点对应的索引
zslGetElementByRank:通过节点查找一个对应的索引
zslParseRange:将给定的min和max对象解析到range中,关于对象在对象系统中总结
zslParseLexRangeItem:从给定对象中解析出一个sds,并作为range中的一个边界使用
zslFreeLexRange:销毁一个sds的range
zslParseLexRange:读取sds类型的range
sdscmplex:比较两个sds的大小
zslLexValueGteMin:根据提供的值判断是否要更新range中的min
zslLexValueLteMax:根据提供的值判断是否要更新range中的max
zslIsInLexRange:判断zskiplist中是否有一部分在sds的range中
zslFirstInLexRange:查找一个zskiplist中第一个出现在sds的range中的节点
zslLastInLexRange:查找一个zskiplist中最后一个出现在sds的range中的节点