有序集合在生活中较常见,如根据成绩对学生进行排名、根据得分对游戏玩家进行排名等。对于有序集合的底层实现,我们可以使用数组、链表、平衡树等结构。数组不便于元素的插入和删除;链表的查询效率低,需要遍历所有元素;平衡树或者红黑树等结构虽然效率高但实现复杂。Redis采用了一种新型的数据结构——跳跃表。跳跃表的效率堪比红黑树,然而其实现却远比红黑树简单。
数据结构

从图中可以看到, 跳跃表主要由以下部分构成:
- 表头(head):负责维护跳跃表的节点指针。
- 跳跃表节点:保存着元素值,以及多个层。
- 层:保存着指向其他元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了提高查找的效率,程序总是从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层次。
- 表尾:全部由
NULL组成,表示跳跃表的末尾。
搜索
跳表的核心思想是**“剪枝”**,具体是如下方式实现
如果是一个简单的链表,那么我们知道在链表中查找一个元素I的话,需要将整个链表遍历一次。
如果是说链表是排序的,并且节点中还存储了“跳跃”的指向后续节点的指针的话,那么在查找一个节点时,仅仅需要遍历N/2个节点即可。
一次典型的skiplist上的查找路径展示

redis中为啥不用红黑树二用跳表
- 内存占用方面跳表比红黑树多,但是多的内存很有限
- 实现比红黑树简单
- 跟红黑树更方便的支持范围查询
跳跃表节点与结构
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
sds ele;// 用于存储字符串类型的数据
double score;//用于存储排序的分值
struct zskiplistNode *backward; // 后退指针,只能指向当前节点最底层的前一个节点,头节点和第一个节点——backward指向NULL,从后向前遍历跳跃表时使用
struct zskiplistLevel {
struct zskiplistNode *forward;//指向本层下一个节点,尾节点的forward指向NULL。
unsigned long span;//forward指向的节点与本节点之间的元素个数。span值越大,跳过的节点个数越多。
} level[];//为柔性数组。每个节点的数组长度不一样,在生成跳跃表节点时,随机生成一个1~64的值,值越大出现的概率越低。
} zskiplistNode;
跳跃表是Redis有序集合的底层实现方式之一,所以每个节点的ele存储有序集合的成员member值,score存储成员score值。所有节点的分值是按从小到大的方式排序的,当有序集合的成员分值相同时,节点会按member的字典序进行排序。
跳跃表的应用
在Redis中,跳跃表主要应用于有序集合的底层实现(有序集合的另一种实现方式为压缩列表)
Redis的配置文件中关于有序集合底层实现的两个配置:
- zset-max-ziplist-entries 128:zset采用压缩列表时,元素个数最大值。默认值为128。
- zset-max-ziplist-value 64:zset采用压缩列表时,每个元素的字符串长度最大值。默认值为64。
zset添加元素的主要逻辑位于t_zset.c的zaddGenericCommand函数中。zset插入第一个元素时,会判断下面两种条件:
- zset-max-ziplist-entries的值是否等于0;
- zset-max-ziplist-value小于要插入元素的字符串长度。
满足任一条件Redis就会采用跳跃表作为底层实现,否则采用压缩列表作为底层实现方式。
一般情况下,不会将zset-max-ziplist-entries配置成0,元素的字符串长度也不会太长,所以在创建有序集合时,默认使用压缩列表的底层实现。
zset新插入元素时,会判断以下两种条件:
- zset中元素个数大于zset_max_ziplist_entries;
- 插入元素的字符串长度大于zset_max_ziplist_value。
当满足任一条件时,Redis便会将zset的底层实现由压缩列表转为跳跃表。
492

被折叠的 条评论
为什么被折叠?



