一、定義
跳躍表(skiplist)是一種有序的資料結構, 它通過在每個節點中維持多個指向其他節點的指標, 從而達到快速訪問節點的目的
跳躍表支援平均o(logn), 最壞o(n) 複雜度的節點查詢, 還可以通過順序性操作來批量處理節點。
redis使用跳躍表作為有序集合鍵的底層實現之一,若乙個有序集合包含的元素數量比較多,或者有序集合中的成員是比較長的字串時,redis就會使用跳躍表來作為有序集合鍵的底層實現。
和鍊錶、字典等資料結構被廣泛地應用在redis內部不同,redis只在兩個地方用到了跳躍表,乙個是實現有序集合鍵,另乙個是在集群結點中用作內部資料結構。除此之外,跳躍表在redis裡面沒有其他用途。
1.1 跳躍表實現
redis跳躍表由redis.h/zskiplistnode 和 redis.h/zskiplist 兩個結構定義, 其中zskiplistnode結構用於表示跳躍表節點, 而zskiplist結構則用來儲存跳躍表的相關資訊, 比如節點的數量, 指向表頭節點和表尾節點的指標等。
下圖為大家展示乙個跳躍表。
如圖所示,最左邊是zskiplist結構,該結構有如下屬性:
typedef
struct zskiplist zskiplist;
header : 指向跳躍表的表頭節點
tail : 指向跳躍表的表尾節點
level : 記錄目前跳躍表內, 層數最大的那個節點的層數(不包括表頭節點的層數)
length : 記錄跳躍表的長度, 也就是跳躍表的節點個數(不包括頭結點)
位於zskiplist結構右方的是四個zskiplistnode結構,該結構包含以下屬性
層(level) : 節點中用l1,l2,l3等字樣標記節點的各個層, l1表示第一層, l2表示第二層, 以此類推. 每層都帶有兩個屬性 : 前進指標和跨度. 前進指標用來訪問位於表尾方向的其他節點, 而跨度則記錄了前進指標所指向節點和當前節點的距離
後退(backward)指標 : 節點中用bw字樣標記節點的後退指標, 它指向位於當前節點的前乙個節點. 後退指標在程式從表尾向表頭遍歷時使用
分值(score) : 各個節點中的1.0, 2.0和3.0是節點所儲存的分值, 在跳躍表中, 節點按各自儲存的分值從小到大排列
成員物件(obj) : 各個節點所儲存的成員物件
typedef struct zskiplistnode level[
];
//後退指標
struct zskiplistnode *backward;
//分值
double score;
//成員物件
robj *obj;
}zskiplistnode;
層 : 跳躍表節點的level陣列可以包含多個元素, 每個元素都包含乙個指向其他節點的指標, 程式可以通過這些層來加快訪問其他節點的速度; 每次建立乙個新跳躍表節點的時候, 程式都根據冪次定律(power law, 越大的數出現概率越小) 隨機產生乙個介於1和32之間的值作為level陣列的大小, 這個大小就是層的高度。
下圖就分別展示了三個高度為1層,3層和5層的節點,因為c語言的陣列索引總是從0開始的,所以節點的第一層是level[0],而第二層是level[1],以此類推。
前進指標 : 每個層都有乙個指向表尾方向的前進指標(level[i].forward屬性),用於從表頭向表尾方向訪問節點。下圖用虛線表示出了程式從表頭向表尾方向,遍歷跳躍表中所有節點的路徑。
跨度 : 用於記錄兩個節點間的距離, 兩個節點之間的跨度越大,它們相距的就越遠。指向null的所有前進指標跨度都為0,因為它們沒有連向任何節點。實際上用來計算排位(rank), 在查詢某個節點的過成功,將沿途訪問過的所有層的跨度累計起來, 得到的結果就是目標節點在跳躍表中的排位。
如下圖所示,虛線標記了在跳躍表中查詢分值為3.0,成員物件為o3的節點時,沿途經歷的層:查詢過程中只經過了乙個層,並且層的跨度為3,所以目標節點在跳躍表中的排位為3。
後退指標 : 用於從表尾到表頭訪問節點,由於每個節點只有乙個後退指標, 所以每次只能後退至前乙個節點。
下圖用虛線展示了如果從表尾向表頭遍歷跳躍表中的所有節點:程式首先通過跳躍表的tail指標訪問表尾節點,然後通過後退指標訪問倒數第二個節點,之後再沿著後退指標訪問倒數第三個節點,在之後遇到指向null的後退指標,於是訪問結束。
分值和成員 : 分值是乙個double型別的資料, 跳躍表的所有節點都按分值從小到大排序; 節點的成員物件是乙個指標, 它指向乙個字串物件, 而字串物件則儲存著乙個sds值; 同乙個跳躍表中, 各個節點儲存的成員物件必須是唯一的, 但是多個節點儲存的分值卻可以是相同的 : 分值相同的節點將按照成員物件在字典序中的大小來進行排序。
如下圖所示,三個跳躍表節點都儲存了相同的分值10086.0,但儲存成員物件o1的節點卻排在儲存o2和o3的節點之前,而儲存成員物件o2的節點又排在儲存成員物件o3的節點之前,由此可見,o1,o2,o3三個成員物件在字典中的排序為o1<=o2<=o3。
二、跳躍表api
Redis設計與實現 四 跳躍表
跳躍表是乙個有序的資料結構,跳躍是通過在當前節點儲存多個其他節點的指標,來達到跳躍的目的。它支援平均o logn 最差o n 複雜度的節點查詢,還可以通過順序性來批量處理節點。在大部分情況下,跳躍表的效率和平衡樹是一樣的,但是邏輯上比平衡樹更加簡單。redis通過跳躍表實現有序集合鍵的底層實現之一。...
《Redis設計與實現》第五章 跳躍表
跳表是一種有序的資料結構,它通過在每個節中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。跳躍表支援平均 logn 最壞o n 時間複雜度的查詢。跳躍表作為有序集合鍵的底層實現之一。redis兩個地方用到了跳躍表 乙個是實現有序集合鍵 另外乙個是在集群節點中用作內部資料結構,除此之外沒有其他...
Redis設計與實現之資料結構與物件 跳躍表
跳躍表 skiplist 是一種有序資料結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。跳躍表支援平均o logn 最壞o n 複雜度的節點查詢,還可以通過順序性操作來批量處理節點。在大部分情況下,跳躍表的效率可以和平衡樹相媲美,並且因為跳躍表的實現比平衡樹要來得更為...