Redis資料結構—跳躍表
大家好,我是白澤,最近學校有點事Redis知識點的更新就放緩了,趁著週六趕緊補一補,我們開始吧~
跳躍表產生的背景
對於有序列表的查詢來說,無法找到類似用在有序陣列上的二分查詢這樣的查詢演算法,因此遍歷的效率比較低,跳躍表的出現就是為了提高有序連結串列的遍歷效率
跳躍表的結構
下圖是概念上的跳躍表,框中的部分是原始的有序連結串列,我們將其進行改造,抽離成多層,屬於同一列的節點的值相同,這就是一個跳躍表的雛形了(L1、L2、L3是每層對應的頭節點,此時共3層),然後我們就能用跳躍表來簡化我們的有序連結串列的查詢操作
此時你應該很疑惑,跳躍表是如何從一個有序單列表抽離成很多層的從而得到跳躍表的,很遺憾,跳躍表的建立將設計概率論的一些知識,我認為現在講解這部分並不合適,所以請假設跳躍表已經由一個有序單列表出發並建立成功了,接下來我們來看看跳躍表是如何簡化有序連結串列的查詢操作的
利用跳躍表查詢有序連結串列
查詢規則:如果查詢目標大於當前值,查當前節點的後一個(同層),如果小於當前值,則下降到當前節點的前一個節點的正下方,並從該節點的後一個開始查詢(正下方節點不用查),如果已經下降到第一層,且查到某個值已經大於查詢目標的值,則表示目標表示不存在,無需繼續查詢
- 現在我們要查詢有序連結串列中是否存在3這個值,此時跳躍表已經抽離成3層,我們從L3頭節點出發,它有一個指向後一個節點的指標,很辛運,後一個節點值為3,查詢結束
- 現在我們查詢有序連結串列中是否存在值為2.5的節點,依舊從第3層的頭節點L3開始查,後一個是值為3的節點,顯然,因為連結串列是有序的,因此往後查將沒有結果,所以下降到第2層的L2節點的後一個節點去查,值為1,繼續查它的後一位,值為3,因此需要再下降到第1層的值為1的節點的後一個節點開始查詢,值為2,繼續查後一個節點,值為3,此時無法下降,查詢結束
Redis跳躍表圖示
Redis的跳躍表本質上就是上面我們提到的跳躍表,它由一個個跳躍表Node節點組成,而整體由一個list表示,由list表示是因為list記錄了listNode網路的頭指標,尾指標,層數,長度資訊,有了list就可以操作跳躍表,這種list的用法出現在絕大多數Redis的資料結構中
光看下面這張圖你可能很疑惑,這和上面的跳躍表結構圖並不相同,別急,往下看~
Redis跳躍表資料結構
跳躍表的list結構實現
typedef struct zskiplist {
struct zskiplistNode *header, *tail;//表頭節點和表尾節點
unsigned long length; //表中的節點數量
int level; //表中層數最大的節點數量
}
跳躍表的Node節點實現
typedef struct zskiplistNode {
//後退指標,指向前一個節點的位置,用於逆向遍歷順序連結串列
struct zskiplistNode *backward;
//分值,因為需要建立有序連結串列,因此需要一個值去度量順序
double score;
//成員物件,用於存放可能需要儲存的物件
robj *obj;
//層
struct zskiplistLevel {
//前進指標
struct zskiplistNode *forward;
//跨度(上圖箭頭上的數值表示跨過了幾個節點)
unsigned int span;
} level[];
} zskiplistNode;
listNode中最重要的是level[]陣列,這個陣列就是圖上每個Node節點的L1~Ln的部分,它的作用就是雖然我們整個Redis跳躍表只有n個節點,但是我們在邏輯上將其抽離成了多層:同一列上節點,各個層其實共用一個節點,並沒有新建立多餘的節點
上面整個跳躍表在Redis中只要4個listNode節點就能組成,且用一個list結構表示,如下:
再說一遍:每個紅框只代表一個listNode節點,而它內部的level[]陣列,舉個例子:
節點A的level[1]的forward屬性表示:如果將節點A當作在第一層上,該節點的後一個節點的指標
同理,節點A的level[2]的forward屬性表示:如果將節點A當作在第二層,該節點的後一個節點的指標
小結
-
你一定要好好理解:level[i]中存放的是第i層上,當前節點的下一個節點的地址,每個節點是被各層共享的,不同的是在各層上,它指向的下一個節點的地址不同
-
每層形成長短不一的有序連結串列,配合下降層數進行有序連結串列的查詢,效率更高