本章將介紹 Redis中 set 和 zset的基本使用和內部原理.由於這兩種數據結構有不少類似的地方因此把他們放到一章中介紹.而且重點介紹zset 內部一個很重要的數據結構:跳躍表.node
先來看看 set 算法
Redis 中 set 集合很像Java 中 HashSet,鍵值對無序、惟1、不爲空.數組
> sadd books Java (integer 1) > sadd books Java (integer 0) # value 值重複 > sadd books Go (integer 1) > smembers books # 無序 1) "Go" 2) "Java"
zset 是 Redis 中最特別的基礎數據結構,其餘幾個都能和 Java 大體對應上.它基本上仍是一個 set 可是添加了一個 score 屬性去保證有序性.其內部實現爲跳躍表稍後將會着重介紹.數據結構
> zadd books 1 Java (integer) 1 > zadd books 2 Go (integer) 1 > zadd books 3 Python (integer) 1 > zrange books 0 -1 #按 score 有序取出 1) "Java" 2) "Go" 3) "Python"
在 zset 中 score 的類型爲 double 因此有時會出現小數點精度問題.post
當 zset 中最後一個 value 被刪除後,這個和 zset 就會被自動刪除,內存被回收.spa
Redis 的 zset 是個複合結構,是由一個 hash 和 skiplist 組成的,其中 hash 用來保存 value 和 score 對應關係.skiplist 用來給 score 排序.關於hash 的內部實現請參閱以前的一篇文章:《你肯定不來了解一下Redis中 Hash的原理嗎》,在這裏咱們着重介紹 skiplist 的實現.指針
由於zset須要高效的插入和刪除,因此底層不適合使用數組實現,須要使用鏈表的結構.當插入新元素時須要根據 score插入到鏈表合適的位置,保證鏈表的有序性.高效的辦法是經過二分查找去找到插入點.code
那麼問題就來了,二分查找的對象必須是有序數組,只有數組支持快速定位,鏈表作不到該怎麼辦呢?這時,就該跳躍表出場了.對象
如圖所示,跳躍表在鏈表的基礎上加入了層級L0~L3的概念,Redis 的跳躍表共有 64 層,可容納 $2^{64}$ 個元素.每一個元素的層級是隨機分配的,分配 L0 的機率是 100%,就是說每一個元素至少會有一層.分配L1 的機率是 50%,分配 L2 的機率是 25%,往上以此類推.排序
每一個 kv 對應的結構爲zslnode.kv 之間使用指針造成有序的雙向鏈表.同一層的 kv 會使用指針串起來.每層元素的遍歷都是從跳躍表的頭指針 kv header 出發.
header 的結構也是 zslnode,當中 value 爲 null,score 爲 Double.MIN_VALUE排在最前面.
struct zslnode{ string value; double score; zslnode*[] forwards; //多層鏈接的指針 zslnode* backward; //回溯指針 } struct zsl{ zslnode* header; //跳躍表頭指針 int maxLevel; //當前節點的最高層 map<String,zslnode*> ht; //hash 中的鍵值對 }
介紹完 skiplist的數據結構後,咱們來具體看下skiplist 是怎樣快速定位元素的.
在上圖中,假設咱們要查找 3 這個節點.skiplist 會從 header 的頂層出發遍歷搜索找到第一個比目標元素小的開始降一層,直到降到最底層找到 3這個節點,搜索路徑爲:
說明:
整個查找過程算法的時間複雜度爲$O(lg(n))$.