跳躍表在redis中主要運用在有序集合和集群節點用作內部資料結構
typedef struct zskiplist
typedef struct zskiplistnodelevel;
}
obj是該結點的成員物件指標,score是該物件的分值,是乙個浮點數,跳躍表中的所有結點,都是根據score從小到大來排序的。
同乙個跳躍表中,各個結點儲存的成員物件必須是唯一的,但是多個結點儲存的分值卻可以是相同的:分值相同的結點將按照成員物件的字典順序從小到大進行排序。
level陣列是乙個柔性陣列成員,它可以包含多個元素,每個元素都包含乙個層指標(level[i].forward),指向該結點在本層的後繼結點。該指標用於從表頭向表尾方向訪問結點。可以通過這些層指標來加快訪問結點的速度。
每次建立乙個新跳躍表結點的時候,程式都根據冪次定律(power law,越大的數出現的概率越小)隨機生成乙個介於1和32之間的值作為level陣列的大小,這個大小就是該結點包含的層數。
redis中的跳躍表,與普通跳躍表的區別之一,就是包含了層跨度(level[i].span)的概念。這是因為在有序集合支援的命令中,有些跟元素在集合中的排名有關,比如獲取元素的排名,根據排名獲取、刪除元素等。通過跳躍表結點的層跨度,可以快速得到該結點在跳躍表中的排名。
計算結點的排名,就是在查詢某個結點的過程中,將沿途訪問過的所有結點的層跨度累加起來,得到的結果就是目標結點在跳躍表中的排名。
層跨度用於記錄本層兩個相鄰結點之間的距離,舉個例子,如下圖的跳躍表:
跳躍表頭結點(header指向的節點)排名為0,之後的節點排名以此類推。在上圖跳躍表中查詢計算分值為3.0、成員物件為o3的結點的排名。查詢過程只遍歷了頭結點的l5層就找到了,並且頭結點該層的跨度為3,因此得到該結點在跳躍表中的排名為3。
如果要查詢分值為2.0、成員物件為o2的結點,查詢結點的過程中,首先經過頭結點的l4層,然後是o1結點的l2層,也就是經過了兩個層跨度為1的結點,因此得到目標結點在跳躍表中的排名為2。跨度算排名
header和tail指標分別指向跳躍表的表頭結點和表尾結點,通過這兩個指標,定位表頭結點和表尾結點的複雜度為o(1)。表尾結點是表中最後乙個結點。而表頭結點實際上是乙個偽結點,該結點的成員物件為null,分值為0,它的層數固定為32(層的最大值)。
length屬性記錄結點的數最,程式可以在o(1)的時間複雜度內返回跳躍表的長度。
level屬性記錄跳躍表的層數,也就是表中層高最大的那個結點的層數,注意,表頭結點的層高並不計算在內。
redis中的跳躍表,與普通跳躍表的另乙個區別就是,每個結點還有乙個前繼指標backward。可用於從表尾向表頭方向訪問結點。通過結點的前繼指標,組成了乙個普通的鍊錶。因為每個結點只有乙個前繼指標,所以只能依次訪問結點,而不能跳過結點。
實現簡單,大資料情況下,時間複雜度o(logn),比起紅黑樹實現簡單
int zslrandomlevel(void)
//while迴圈跳出時,用update[i]記錄第i層所遍歷到的最後乙個節點,遍歷到i=0時,就要在該節點後要插入節點
update[i] = x;
}
上面**邏輯很簡單,對照下面的圖來看,從層數最高開始往下遍歷,先算出這個層數的排名rank,然後放入update,
在上圖的跳躍表中,假設現在要插入的結點分數為17,黃色虛線所標註的,就是插入新結點的位置。下面標註紅色的,就是在每層找到的插入結點的前驅結點,記錄在update[i]中,而rank[i]記錄了update[i]在跳躍表中的排名,因此,rank[4] = 3, rank[3] = 3, rank[2] = 4, rank[1] = 4, rank[0] = 4。
然後
level = zslrandomlevel(); //獲得乙個隨機的層數
if (level > zsl->level)
zsl->level = level; //更新表中的最大成數值
}
如果新增的新節點層數比跳躍表最大層數要高,那麼高出的部分就要賦值,就要新增前驅節點,更新update元素數量,可以明顯看到rank和update都在增多,而且更新值為0和header,說明,高出部分前驅都是表頭,表頭跨度也就正好是節點數量,注意是原來節點數量,新增節點還沒加入,這裡表頭直接指向null,跨度最長,後面這裡還會再修改
然後繼續
x = zslcreatenode(level,score,obj); //建立乙個節點
for (i = 0; i < level; i++)
前兩條語句先更新指向,這裡和鍊錶插入一樣
後兩條語句更新跨度,自己用上面的陣列值帶入即可明白
rank[0] - rank[i]等於距離,前乙個節點的排名與當前前驅層數節點的的距離
/* increment span for untouched levels */
for (i = level; i < zsl->level; i++)
//設定插入節點的後退指標,就是查詢時最下層的最後乙個節點,該節點的位址記錄在update[0]中
//如果插入在第二個節點,也就是頭結點後的位置就將後退指標設定為null
x->backward = (update[0] == zsl->header) ? null : update[0];
if (x->level[0].forward) //如果x節點不是最尾部的節點
x->level[0].forward->backward = x; //就將x節點後面的節點的後退節點設定成為x位址
else
zsl->tail = x; //否則更新表頭的tail指標,指向最尾部的節點x
zsl->length++; //跳躍表節點計數器加1
剩下就很簡單了
因為比較感興趣redis跳躍表插入是怎麼插入的,所以研究了下,圖都是盜的,參考了些資料,總算搞明白了,redis的跳躍表和傳統跳躍表是有不同的,所以想搞懂一下
關鍵點還是在於兩個陣列,rank陣列和update陣列
redis資料結構 跳躍表
跳躍表 skiplist 是一種有序資料鏈表結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。查詢平均效能為o logn 最壞的情況會出現o n 情況,而redis中的zset在資料較多的時候底層就是採用跳躍表去實現的,元素較少的時候會進行小物件壓縮採用壓縮列表實現。小...
Redis資料結構 跳躍表
跳躍表是一種有序資料結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。redis使用跳躍表作為有序集合鍵的底層實現之一,如果乙個有序集合包含的元素數量比較多,又或者有序集合中的元素的成員是比較長的字串時,redis就會使用跳躍表作為有序集合鍵的底層實現。redis的跳躍...
Redis資料結構 skiplist(跳躍表)
跳躍表在redis中主要用於有序集合鍵的實現,其他地方沒怎麼用到,但是這種資料結構在面試的時候經常會問到,因為它作為一種查詢時間複雜度為o logn 的特殊的鍊錶,效率堪比紅黑樹或平衡樹,而實現難度卻遠小於它們。下面分3個模組講解redis的跳躍表實現 一 跳躍表的應用場景 在redis中,當有序集...