Redis数据结构学习-跳表(四)

跳表

跳表 skiplist 是一种有序的数据结构, 通过在每个节点中维持多个指向其它节点的指针、达到快速访问节点的目的

支持平均 O(logN), 最坏 O(N)复杂度的查找, 还支持顺序性操作来批量处理节点, 大部分情况下、效率可以合平衡树相媲美, 且实现简单, 很多程序都可以使用skipList替代平衡树, Redis使用跳跃表作为有序集合键的底层实现之一.

跳表只在两个地方使用: 实现有序集合键 & 在集群节点中用作内部结构

跳表的实现

Redis 的跳跃表由 redis.h/zskiplistNoderedis.h/zskiplist 两个结构定义, zskiplistNode 结构用于表示跳跃表节点, zskiplist 则用于保存跳跃表节点的相关信息, eg.

节点梳理、及指向表头节点 和 表尾节点的指针.

跳表示意图.png

最左边是跳表的zskiplist结构, 包含下边这些属性:

header: 指向跳表的表头节点

tail: 指向跳表的表尾节点

level: 记录当前跳表内、层次最大的那个节点的层数(表头节点的层数不计算在内)

length: 记录跳表的长度, 即: 目前跳表内包含的节点的数量(表头节点不计)

右边是4个zskiplistNode结构, 属性如下:

level: 节点中用 L1, L2, L3 等标记节点的各个层, L1代表第一层, 依次类推. 每个层有两个属性: 前进指针跨度. 前进指针用于访问位于表尾方向的其它节点, 跨度记录前进指针指向的节点和当前节点的距离. 程序从表头向表尾遍历时, 访问会沿着层的前进指针进行.

backward: 后退指针, 节点中BW标记的指针, 指向当前节点的前一个指针, 后退指针在程序从表尾向表头遍历时使用

score: 分值, 节点中的1.0, 2.0, 3.0是各节点保存的分值, 在跳表中节点按各自保存的分值从小到大排列.

obj: 成员对象, 各个节点中的o1, o2 和 o3 是节点保存的成员对象.

注意: 表头节点和其它节点的构造是一致的, 也有后退指针, 分值 和 成员对象, 但不会被使用.

跳跃表节点

redis.h/zskiplistNode结构定义

typedef struct zskiplistNode {
  // 层
  struct zskiplistLevel {
    struct zskiplistNode *forward; // 前进指针
    unsigned int span; // 跨度
  }level[];
  struct zskiplistNode *backward; // 后退指针
  double score; // 分值
  robj *obj; // 成员对象
} zskiplistNode;

跳跃表节点的level数组可以包含多个元素, 每个元素都包含一个指向其它节点的指针, 程序可以通过这些层来加速访问其它节点的速度, 一般来说、层的数量越多, 访问其它节点的速度就越快. 每次创建一个新的跳表时、程序根据幂次定律随机生成一个介于1和32之间的值作为level数组的大小, 即 层高

前进指针 每一个层都有一个指向表尾方向的前进指针(level[i].forward), 用于从表头向表尾方向访问节点.

跨度 (level[i].span)用于记录两个节点之间的距离, 两个节点之间的跨度越大,相距就越远. 指向null的所有前进指针的跨度都为0, 因为他们没有连向任何节点

后退指针 (backward)用于从表尾向表头方向访问节点, 跟一次可以跳多个节点的前进指针不同, 每个节点只有一个后退指针、只能后退至前一个节点.

分值和成员 (score)是一个double类型的浮点数, 跳跃表中所有的节点都按分值从小到大来排序(obj)是一个指针、指向一个字符串对象、字符串对象中则保存着一个SDS

在同一个跳表中、各节点保存的成员对象必须是唯一的、但多个节点保存的分值却是可以相同的. 分值相同的节点按照成员对象在字典序中的大小排序, 小的节点放在前边.

跳跃表

仅靠多个跳跃表节点就可以完成一个跳表、但使用一个zskiplist结构来持有这些节点, 程序可以更方便的对整个跳表进行处理, eg. 快速访问跳表的头结点和尾结点、快速获取节点数量.

typedef struct zskiplist {
  struct zskiplistNode *header, *tail; // 表头节点和表尾节点
  unsigned long length; // 表中节点的数量
  int level; // 表中层数最大的节点的层数
} zskiplist;

headertail 指针分别指向表头和表尾节点, 程序定位表头节点和表尾节点的复杂度是 O(1)

通过length属性记录节点的数量、程序可以在 O(1) 复杂度内返回跳表长度

level 属性则用于在 O(1)复杂度内获取跳表中层高最大的节点的层数量(表头节点的层高不计)

重点回顾

  1. 跳表是有序集合的底层实现之一
  2. Redis的跳表实现由 zskiplistzskiplistNode 两个结构组成, 其中 zskiplist 用于保存跳表信息(eg. 表头节点、表尾节点、长度)、而zskiplistNode 则用于表示跳跃表节点
  3. 每个跳跃表节点的层高都是 1~32 之间的随机数
  4. 同一个跳表中、多个节点可以包含相同的分值、但每个节点的成员对象必须是唯一的
  5. 跳表中的节点按照分值大小排序、分值相同时、节点按照成员对象的大小排序
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容