//跳躍表節點
/* zsets use a specialized version of skiplists */
typedef
struct zskiplistnode level;
// 該節點擁有的層級陣列 (柔性陣列)
} zskiplistnode;
// 跳躍表結構
typedef
struct zskiplist zskiplist;
#define zskiplist_maxlevel 32
/* should be enough for 2^32 elements */
#define zskiplist_p 0.25
/* skiplist p = 1/4 */
可以先看下有序集合的結構,其中就包含跳躍表的實現。後面再單獨分析有序集合zset。
// 有序集合
typedef
struct zset zset;
先根據個人理解,畫一張跳躍表的圖,為了更好的理解下面的內容。
跳躍表除去「跳躍」的特性本身是個鍊錶,「跳躍」由level這個層級化的資訊中的指標帶來的。
程式設計上,總是從高層級開始訪問,相當於大範圍的查詢,隨著查詢範圍縮小,慢慢的降低層級,有一種跳著查的感覺。
zslcreatenode
建立跳躍表節點
// 層數level 分數score 物件是obj
zskiplistnode *
zslcreatenode
(int level,
double score, robj *obj)
zslcreate
建立乙個跳躍表
zskiplist *
zslcreate
(void
)// 頭結點的後退指標null
zsl->header->backward =
null
;// 尾結點
zsl->tail =
null
;return zsl;
}
zslrandomlevel
隨機獲取乙個level,後面再細講。
zslinsert
插入redis物件
zskiplistnode *
zslinsert
(zskiplist *zsl,
double score, robj *obj)
// 記錄將要和新節點相連線的節點
update[i]
= x;
}// 新節點的層數,通過隨機值獲取
level =
zslrandomlevel()
;// 如果新節點的層數大於當前跳躍表的最高層數
if(level > zsl->level)
// 更新表中節點最大層數
zsl->level = level;
}// 建立新節點
x =zslcreatenode
(level,score,obj)
;// 將前面記錄的指標指向新節點,並做相應的設定
for(i =
0; i < level; i++
)// 未接觸的節點的 span 值也需要增一,這些節點直接從表頭指向新節點
for(i = level; i < zsl->level; i++
)// 設定新節點的後退指標
x->backward =
(update[0]
== zsl->header)
?null
: update[0]
;if(x->level[0]
.forward)
x->level[0]
.forward->backward = x;
else
zsl->tail = x;
// 跳躍表的節點計數增一
zsl->length++
;return x;
}
zsldeletenode
刪除指定節點
zsldelete
刪除redis物件(先查詢,再呼叫zsldeletenode)
zslfreenode
釋放指定的跳躍表節點
zslfree
釋放指定的跳躍表
// 其中遍歷 所有節點的**如下
// 因為物件都在第一層,只要一直遍歷前進指標即可
while
(node)
先看乙個簡單的有序鍊錶:
在這樣乙個鍊錶中,如果要查詢某個資料,就需要從頭開始逐個比較,直到找到指定的節點。時間複雜度是o(n)。
現在假設每兩個節點,增加乙個指標。在這樣乙個鍊錶中,如果要查詢某個資料,同樣從頭開始逐個比較,但是它一次會跳過兩個節點,相比上乙個鍊錶,整體時間複雜度變成了o(n/2)。
按照這個思路,每三個節點再增加乙個指標,讓跳躍距離更大,這樣就擁有了更快的查詢能力。可以想象,當鍊表的長度比較大的時候,這樣多個層次的鍊錶進行「跳躍式」的查詢,能跳過很多下層的節點,大大提高查詢速度。
記下來思考,我們該怎麼去構建這個跳躍鍊錶,真的按照上面的想法,每幾個節點乙個迴圈這樣有序的高度變化嗎?
試想一下如果這麼做,新插入乙個節點,該怎麼維持原先的迴圈有序呢,需要調整新節點以及後面所有的節點。刪除乙個節點同理。那麼維護這樣乙個鍊錶,就變的更加麻煩。實際上,redis為了避免這個問題,採取的隨機高度的做法,每個節點的高度都是隨機性的,在整體上實現每個層次的數量級分化。
上面在讀redis的跳躍表時,有乙個函式是zslrandomlevel
。具體**如下:
// zskiplist_maxlevel=32
// zskiplist_p=0.25
intzslrandomlevel
(void
)
這個函式,在每個新增節點時都會呼叫,也就是每個節點都會隨機出乙個level高度。
怎麼理解這件事。
函式中有乙個迴圈,條件是隨機數小於1/4,高度就增加1。從巨集觀角度上來看,假設有1000個節點,那麼就大概會有250個節點高度大於1,62個節點高度大於2,16個節點高度大於3… 大致就是每一層之上的節點數大約是這一層節點數的
1/4,這樣整體保證了這個跳躍表不會過於臃腫也不會失去跳躍性。
redis取1/4,這個數字不是固定的,在自己的設計中,也可以更改,是乙個彈性設計。
上圖是乙個redis中可能生成的跳躍表示例,現在來查詢9,需要的步驟是1->4->6->9,只需要4次就找到對應目標,查詢示例如下
redis zskiplist 跳躍表原始碼學習
typedef struct zskiplistnode level index越大表示層級越高 zskiplistnode typedef struct zskiplist zskiplist typedef struct zset zset 命令 zadd key score value zsk...
redis原始碼解析 跳躍表
定義 跳躍表是一種有序資料結構,它通過在每個節點中維護多個指向其他節點的指標,從而達到快速訪問節點的目的。跳躍表支援平均o logn 最壞o n 複雜度的節點查詢,大部分情況下,跳躍表的效率可以和平衡樹相媲美,並且因為跳躍表的實現比平衡樹要來得簡單,所以有不少程式都使用跳躍表來替代平衡樹。從圖中可以...
redis原始碼學習之跳躍表
跳躍表對於我來說是乙個比較陌生的資料結構,因此花了一上午的時間先看了一蛤mit的公開課。網易雲課堂 mit跳躍表 什麼是跳躍表,有乙個很簡單的例子,有些地方的火車站跟高鐵站是同乙個站,有的地方只有火車站 假設現在的線路是a b c d e。其中a和c剛剛說的高鐵和火車站在一塊,其他的只有火車站,考慮...