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層上,當前節點的下一個節點的地址,每個節點是被各層共享的,不同的是在各層上,它指向的下一個節點的地址不同
-
每層形成長短不一的有序鏈表,配合下降層數進行有序鏈表的查詢,效率更高