虚线 实现_redis跳跃表实现
跳躍表是一種有序的數(shù)據(jù)結(jié)構(gòu),它通過(guò)在每個(gè)節(jié)點(diǎn)中維持多個(gè)指向其他節(jié)點(diǎn)的指針,從而達(dá)到快速訪問(wèn)節(jié)點(diǎn)的目的。
redis 使用跳躍表作為有序集合鍵的底層實(shí)現(xiàn)之一,如果一個(gè)有序集合包含的元素?cái)?shù)量比較多,又或者有序集合中元素的成員是比較長(zhǎng)的字符串時(shí),redis 就會(huì)使用跳躍表來(lái)作為有序集合鍵的底層實(shí)現(xiàn)。
redis 只在兩個(gè)地方使用到了跳躍表,一個(gè)是實(shí)現(xiàn)有序集合鍵,另一個(gè)是在集群節(jié)點(diǎn)中用作內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
1? ?跳躍表的實(shí)現(xiàn)
redis 的跳躍表由? redis.h/zskiplistNode?和?redis.h/zskiplist 兩個(gè)結(jié)構(gòu)定義,其中 zskiplistNode 結(jié)構(gòu)用于表示跳躍表節(jié)點(diǎn),而 zskiplist 結(jié)構(gòu)則用于保存跳躍表節(jié)點(diǎn)的相關(guān)信息,比如節(jié)點(diǎn)的數(shù)量,以及指向表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)的指針等等。
上面最左邊是一個(gè)??zskiplist?結(jié)構(gòu)。該結(jié)構(gòu)包含以下屬性:
1、header:指向跳躍表的表頭節(jié)點(diǎn)。
2、tail:指向跳躍表的表尾節(jié)點(diǎn)。
3、level:記錄目前跳躍表內(nèi),層數(shù)最大的那個(gè)節(jié)點(diǎn)的層數(shù)(表頭節(jié)點(diǎn)的層數(shù)不計(jì)算在內(nèi))。
4、length:記錄跳躍表的長(zhǎng)度,也就是跳躍表目前包含節(jié)點(diǎn)的數(shù)量(表頭節(jié)點(diǎn)的層數(shù)不計(jì)算在內(nèi))。
位于 zskiplist 右方的是? 4 個(gè) zskiplistNode 結(jié)構(gòu)。該結(jié)構(gòu)包含以下屬性:
1、層(level):節(jié)點(diǎn)中用 L1、L2 等字樣標(biāo)記節(jié)點(diǎn)的各個(gè)層,L1 代表第一層,L2 代表第二層,以此類(lèi)推。每個(gè)層都帶有兩個(gè)屬性:前進(jìn)指針和跨度。
前進(jìn)指針用于訪問(wèn)位于表尾方向的其他節(jié)點(diǎn),而跨度則記錄了前進(jìn)指針?biāo)赶蚬?jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的距離。在上面圖片中,連線上帶有數(shù)字的箭頭就代表前進(jìn)指針,而那個(gè)數(shù)字則代表跨度。
2、后退(backward)指針:節(jié)點(diǎn)中用 BW?字樣標(biāo)記節(jié)點(diǎn)的后退指針,它指向位于當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)。后退指針在程序從表尾向表頭遍歷時(shí)使用。
3、分值(score):各個(gè)節(jié)點(diǎn)中的 1.0、2.0 是節(jié)點(diǎn)所保存的分值。在跳躍表中,節(jié)點(diǎn)按各自保存的分值從小到大排列。
4、成員對(duì)象(obj):各個(gè)節(jié)點(diǎn)的 o1、o2 是節(jié)點(diǎn)保存的成員對(duì)象。
注意:
表頭節(jié)點(diǎn)跟其他節(jié)點(diǎn)的構(gòu)造是一樣的,只不過(guò)表頭節(jié)點(diǎn)的這些屬性不會(huì)用到,因此省略了這部分。
1.1? ? 跳躍表節(jié)點(diǎn)
跳躍表節(jié)點(diǎn)的實(shí)現(xiàn)由 redis.h/ 在?skiplistNode 結(jié)構(gòu)定義:
typedef struct zskiplistNode {????// 后退指針????struct?zskiplistNode *backward;????// 分值????double score;????//?成員對(duì)象????robj *obj;????// 層????struct?zskiplistLevel {???? // 前進(jìn)指針????????struct?zskiplistNode?*froward;????????// 跨度????????unsigned int span;?????}?level[];}?zskiplistNode;1、層
跳躍表節(jié)點(diǎn)的 level 數(shù)組可以包含多個(gè)元素,每個(gè)元素都包含一個(gè)指向其他節(jié)點(diǎn)的指針,程序可以通過(guò)這些層來(lái)加快訪問(wèn)其他節(jié)點(diǎn)的速度。一般來(lái)說(shuō),層的數(shù)量越多,訪問(wèn)其他節(jié)點(diǎn)的速度就越快。
每次創(chuàng)建一個(gè)新的跳躍表節(jié)點(diǎn)的時(shí)候,程序都會(huì)根據(jù)冪次定律(越大的數(shù)出現(xiàn)的概率越小)隨機(jī)生成一個(gè)介于 1 和 32 之間的值作為?level?數(shù)組的大小,這個(gè)大小就是層的“高度”。
2、前進(jìn)指針
每個(gè)層都有一個(gè)指向表尾方向的前進(jìn)(level[i].forward 屬性),用于從表頭向表尾方向訪問(wèn)節(jié)點(diǎn)。
下面展示了帶有不同層高的節(jié)點(diǎn):
下面用虛線表示出了程序從表頭向表尾方向,遍歷跳躍表中所有節(jié)點(diǎn)的路徑:
1、迭代程序首先訪問(wèn)跳躍表的第一個(gè)節(jié)點(diǎn)(表頭),然后從第四層的前進(jìn)指針移動(dòng)到表中的第二個(gè)節(jié)點(diǎn)。?
2、在第二個(gè)節(jié)點(diǎn)時(shí),程序沿著第二層的前進(jìn)指針移動(dòng)到表中的第三個(gè)節(jié)點(diǎn)。?
3、?在第三個(gè)節(jié)點(diǎn)時(shí),程序同樣沿著第二層的前進(jìn)指針移動(dòng)到表中的第四個(gè)節(jié)點(diǎn)。?
4、當(dāng)程序再次沿著第四個(gè)節(jié)點(diǎn)的前進(jìn)指針移動(dòng)時(shí),它碰到一個(gè)?NULL,程序知道這時(shí)已經(jīng)到達(dá)了跳躍表的表尾,于是結(jié)束這次遍歷。
3、跨度
層的跨度(?level[i].span 屬性)用于記錄兩個(gè)節(jié)點(diǎn)之間的距離:
1、兩個(gè)節(jié)點(diǎn)之間的跨度越大,它們相距得就越遠(yuǎn)。
2、指向?NULL 的所有前進(jìn)指針的跨度都為0,因?yàn)樗鼈儧](méi)有連向任何節(jié)點(diǎn)。
跨度實(shí)際上是用來(lái)計(jì)算排位(rank)的。在查找某個(gè)節(jié)點(diǎn)的過(guò)程中,將沿途訪問(wèn)過(guò)的所有層的跨度累計(jì)起來(lái),得到的結(jié)果就是目標(biāo)節(jié)點(diǎn)在跳躍表中的排位。
舉個(gè)例子,下圖用虛線標(biāo)記了在跳躍表中查找分值為?2.0、成員對(duì)象為?o2 的節(jié)點(diǎn)時(shí),沿途經(jīng)歷的層:在查找節(jié)點(diǎn)的過(guò)程中,程序經(jīng)過(guò)了兩個(gè)跨度為1的節(jié)點(diǎn),因此可以計(jì)算出,目標(biāo)節(jié)點(diǎn)在跳躍表中的排位為2。
4、后退指針
節(jié)點(diǎn)的后退指針(backward 屬性)用于從表尾向表頭方向訪問(wèn)節(jié)點(diǎn)。
跟可以一次跳過(guò)多個(gè)節(jié)點(diǎn)的前進(jìn)指針不同,因?yàn)槊總€(gè)節(jié)點(diǎn)只有一個(gè)后退指針,所以每次只能后退至前一個(gè)節(jié)點(diǎn)。
下圖用虛線展示了如何從表尾向表頭遍歷跳躍表中的所有節(jié)點(diǎn):
程序首先通過(guò)跳躍表的tail指針訪問(wèn)表尾節(jié)點(diǎn),然后通過(guò)后退指針訪問(wèn)倒數(shù)第二個(gè)節(jié)點(diǎn),之后再沿著后退指針訪問(wèn)倒數(shù)第三個(gè)節(jié)點(diǎn),再之后遇到指向?NULL 的后退指針,于是訪問(wèn)結(jié)束。
5、分值和成員
節(jié)點(diǎn)的分值(score屬性)是一個(gè)?double 類(lèi)型的浮點(diǎn)數(shù),跳躍表中的所有節(jié)點(diǎn)都按分值從小到大來(lái)排序。
節(jié)點(diǎn)的成員對(duì)象(obj屬性)是一個(gè)指針,它指向一個(gè)字符串對(duì)象,而字符串對(duì)象則保存著一個(gè)?SDS 值。
在同一個(gè)跳躍表中,各個(gè)節(jié)點(diǎn)保存的成員對(duì)象必須是唯一的,但是多個(gè)節(jié)點(diǎn)保存的分值卻可以是相同的:
分值相同的節(jié)點(diǎn)將按照成員對(duì)象在字典中的大小來(lái)進(jìn)行排序,成員對(duì)象較小的節(jié)點(diǎn)會(huì)排在前面(靠近表頭的方向),而成員對(duì)象較大的節(jié)點(diǎn)則會(huì)排在后面(靠近表尾的方向)。
在下圖中所示的跳躍表中,三個(gè)跳躍表節(jié)點(diǎn)都保存了相同的分值10086.0。
但保存成員對(duì)象o1的節(jié)點(diǎn)卻排在保存成員對(duì)象o2和o3的節(jié)點(diǎn)的前面,而保存成員對(duì)象o2的節(jié)點(diǎn)又排在保存成員對(duì)象o3的節(jié)點(diǎn)之前,由此可見(jiàn),o1、o2、o3三個(gè)成員對(duì)象在字典中的排序?yàn)閛1<=o2<=o3。
1.2????跳躍表
靠多個(gè)跳躍表節(jié)點(diǎn)就可以組成一個(gè)跳躍表,如下所示:
zskiplist 結(jié)構(gòu)的定義如下:
typedef struct zskiplist { // 表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn) structz skiplistNode *header, *tail; // 表中節(jié)點(diǎn)的數(shù)量 unsigned long length; // 表中層數(shù)最大的節(jié)點(diǎn)的層數(shù) int level;} zskiplist;通過(guò)使用一個(gè)?zskiplist 結(jié)構(gòu)來(lái)持有這些節(jié)點(diǎn),程序可以更方便地對(duì)整個(gè)跳躍表進(jìn)行處理。比如快速訪問(wèn)跳躍表的表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn),或者快速地獲取跳躍表節(jié)點(diǎn)的數(shù)量(也即是跳躍表的長(zhǎng)度)等信息,如下圖所示:
這樣獲取表頭、表尾節(jié)點(diǎn),表長(zhǎng),以及表中最高層數(shù)的復(fù)雜度均為?O(1)。
2? ?跳躍表 API
3? ? 回顧
跳躍表是有序集合的底層實(shí)現(xiàn)之一, 除此之外它在 Redis 中沒(méi)有其他應(yīng)用。
Redis 的跳躍表實(shí)現(xiàn)由?zskiplist?和?zskiplistNode?兩個(gè)結(jié)構(gòu)組成, 其中?zskiplist?用于保存跳躍表信息(比如表頭節(jié)點(diǎn)、表尾節(jié)點(diǎn)、長(zhǎng)度), 而?zskiplistNode?則用于表示跳躍表節(jié)點(diǎn)。
每個(gè)跳躍表節(jié)點(diǎn)的層高都是?1?至?32?之間的隨機(jī)數(shù)。
在同一個(gè)跳躍表中, 多個(gè)節(jié)點(diǎn)可以包含相同的分值, 但每個(gè)節(jié)點(diǎn)的成員對(duì)象必須是唯一的。
跳躍表中的節(jié)點(diǎn)按照分值大小進(jìn)行排序, 當(dāng)分值相同時(shí), 節(jié)點(diǎn)按照成員對(duì)象的大小進(jìn)行排序。
參考資料
[1]??redis設(shè)計(jì)與實(shí)現(xiàn)
總結(jié)
以上是生活随笔為你收集整理的虚线 实现_redis跳跃表实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: RISC-V架构国产芯片10连发:4款基
- 下一篇: 楼层效果_1一28高楼最好最吉利的楼层是