本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或从新修改使用,但须要注明来源。 署名 4.0 国际 (CC BY 4.0)redis
本文做者: Nicksxs数组
建立时间: 2020年01月04日数据结构
本文连接: redis数据结构介绍二-第二部分 跳表dom
跳表是个在咱们平常的代码中不太经常使用到的数据结构,相对来说就没有像数组,链表,字典,散列,树等结构那么熟悉,因此就从头开始分析下,首先是链表,跳表跟链表都有个表字(太硬扯了我🤦♀️),注意这是个有序链表
如上图,在这个链表里若是我要找到 23,是否是我须要从3,5,9开始一直日后找直到找到 23,也就是说时间复杂度是 O(N),N 的一次幂复杂度,那么咱们来看看第二个
这个结构跟原先有点不同,它给链表中偶数位的节点又加了一个指针把它们连接起来,这样子当咱们要寻找 23 的时候就能够从原来的一个个往下找变成跳着找,先找到 5,而后是 10,接着是 19,而后是 28,这时候发现 28 比 23 大了,那我在退回到 19,而后从下一层原来的链表往前找,
这里毛估估是否是前面的节点我就少找了一半,有那么点二分法的意思。
前面的实际上是跳表的引子,真正的跳表其实不是这样,由于上面的其实有个比较大的问题,就是插入一个元素后须要调整每一个元素的指针,在 redis 中的跳表实际上是作了个随机层数的优化,由于沿着前面的例子,其实当数据量很大的时候,是否是层数越多,其查询效率越高,可是随着层数变多,要保持这种严格的层数规则其实也会增大处理复杂度,因此 redis 插入每一个元素的时候都是使用随机的方式,看一眼代码优化
/* ZSETs use a specialized version of Skiplists */ typedef struct zskiplistNode { sds ele; double score; struct zskiplistNode *backward; struct zskiplistLevel { struct zskiplistNode *forward; unsigned long span; } level[]; } zskiplistNode; typedef struct zskiplist { struct zskiplistNode *header, *tail; unsigned long length; int level; } zskiplist; typedef struct zset { dict *dict; zskiplist *zsl; } zset;
忘了说了,redis 是把 skiplist 跳表用在 zset 里,zset 是个有序的集合,能够看到 zskiplist 就是个跳表的结构,里面用 header 保存跳表的表头,tail 保存表尾,还有长度和最大层级,具体的跳表节点元素使用 zskiplistNode 表示,里面包含了 sds 类型的元素值,double 类型的分值,用来排序,一个 backward 后向指针和一个 zskiplistLevel 数组,每一个 level 包含了一个前向指针,和一个 span,span 表示的是跳表前向指针的跨度,这里再补充一点,前面说了为了灵活这个跳表的新增修改,redis 使用了随机层高的方式插入新节点,可是若是全部节点都随机到很高的层级或者全部都很低的话,跳表的效率优点就会减少,因此 redis 使用了个小技巧,贴下代码spa
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ int zslRandomLevel(void) { int level = 1; while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) level += 1; return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; }
当随机值跟0xFFFF进行与操做小于ZSKIPLIST_P * 0xFFFF时才会增大 level 的值,所以保持了一个相对递减的几率
能够简单分析下,当 random() 的值小于 0xFFFF 的 1/4,才会 level + 1,就意味着当有 1 - 1/4也就是3/4的几率是直接跳出,因此一层的几率是3/4,也就是 1-P,二层的几率是 P(1-P),三层的几率是 P² (1-P) 依次递推。指针