redis源碼剖析(四)跳表

文章目錄

    • 整數集合
    • 跳躍表
    • 壓縮列表
    • 總結

整數集合

當一個集合只包含整數,且這個集合的元素不多的時候,Redis 就會使用整數集合 intset 。首先看 intset 的數據結構:

typedef struct intset {// 編碼方式uint32_t encoding;// 集合包含的元素數量uint32_t length;// 保存元素的數組int8_t contents[];
} intset;

其實 intset 的數據結構比較好理解。一個數據保存元素,length 保存元素的數量,也就是contents的大小,encoding 用于保存數據的編碼方式。

通過代碼我們可以知道,encoding 的編碼類型包括了:

#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

實際上我們可以看出來。 Redis encoding的類型,就是指數據的大小。作為一個內存數據庫,采用這種設計就是為了節約內存。

既然有從小到大的三個數據結構,在插入數據的時候盡可能使用小的數據結構來節約內存,如果插入的數據大于原有的數據結構,就會觸發擴容。

擴容有三個步驟:

  1. 根據新元素的類型,修改整個數組的數據類型,并重新分配空間
  2. 將原有的的數據,裝換為新的數據類型,重新放到應該在的位置上,且保存順序性
  3. 再插入新元素

整數集合不支持降級操作,一旦升級就不能降級了。

跳躍表

跳躍表是鏈表的一種,是一種利用空間換時間的數據結構。跳表平均支持 O(logN),最壞O(N)復雜度的查找。

跳表是由一個zskiplist 和 多個 zskiplistNode 組成。我們先看看他們的結構:

/* ZSETs use a specialized version of Skiplists */
/** 跳躍表節點*/
typedef struct zskiplistNode {// 成員對象robj *obj;// 分值double score;// 后退指針struct zskiplistNode *backward;// 層struct zskiplistLevel {// 前進指針struct zskiplistNode *forward;// 跨度unsigned int span;} level[];} zskiplistNode;/** 跳躍表*/
typedef struct zskiplist {// 表頭節點和表尾節點struct zskiplistNode *header, *tail;// 表中節點的數量unsigned long length;// 表中層數最大的節點的層數int level;} zskiplist;

所以根據這個代碼我們可以畫出如下的結構圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8veqZuZy-1573628505436)(media/15663755251342/15663757297856.jpg)]

其實跳表就是一個利用空間換時間的數據結構,利用 level 作為鏈表的索引。

之前有人問過 Redis 的作者 為什么使用跳躍表,而不是 tree 來構建索引?作者的回答是:

  1. 省內存。
  2. 服務于 ZRANGE 或者 ZREVRANGE 是一個典型的鏈表場景。時間復雜度的表現和平衡樹差不多。
  3. 最重要的一點是跳躍表的實現很簡單就能達到 O(logN)的級別。

壓縮列表

壓縮鏈表 Redis 作者的介紹是,為了盡可能節約內存設計出來的雙向鏈表。
對于一個壓縮列表代碼里注釋給出的數據結構如下:
在這里插入圖片描述

zlbytes 表示的是整個壓縮列表使用的內存字節數zltail 指定了壓縮列表的尾節點的偏移量zllen 是壓縮列表 entry 的數量entry 就是 ziplist 的節點zlend 標記壓縮列表的末端

這個列表中還有單個指針:

ZIPLIST_ENTRY_HEAD 列表開始節點的頭偏移量ZIPLIST_ENTRY_TAIL 列表結束節點的頭偏移量ZIPLIST_ENTRY_END 列表的尾節點結束的偏移量

再看看一個 entry 的結構:

/** 保存 ziplist 節點信息的結構*/
typedef struct zlentry {// prevrawlen :前置節點的長度// prevrawlensize :編碼 prevrawlen 所需的字節大小unsigned int prevrawlensize, prevrawlen;// len :當前節點值的長度// lensize :編碼 len 所需的字節大小unsigned int lensize, len;// 當前節點 header 的大小// 等于 prevrawlensize + lensizeunsigned int headersize;// 當前節點值所使用的編碼類型unsigned char encoding;// 指向當前節點的指針unsigned char *p;} zlentry;

依次解釋一下這幾個參數。

prevrawlen 前置節點的長度,這里多了一個 size,其實是記錄了 prevrawlen 的尺寸。Redis 為了節約內存并不是直接使用默認的 int 的長度,而是逐漸升級的。
同理 len 記錄的是當前節點的長度,lensize 記錄的是 len 的長度。
headersize 就是前文提到的兩個 size 之和。
encoding 就是這個節點的數據類型。這里注意一下 encoding 的類型只包括整數和字符串。
p 節點的指針,不用過多的解釋。

需要注意一點,因為每個節點都保存了前一個節點的長度,如果發生了更新或者刪除節點,則這個節點之后的數據也需要修改,有一種最壞的情況就是如果每個節點都處于需要擴容的零界點,就會造成這個節點之后的節點都要修改 size 這個參數,引發連鎖反應。這個時候就是 壓縮鏈表最壞的時間復雜度 O(n^2)。 不過所有節點都處于臨界值,這樣的概率可以說比較小。

總結

至此Redis的基本數據結構就介紹完了。我們可以看到 Redis 對內存的使用真是“斤斤計較”,對于內存是使用特別節約。同時 Redis 作為一個單線程應用,不用考慮并發的問題,將很多類似 size 或者 length 的參數暴露出來,將很多 O(n) 的操作降低為 O(1)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/382371.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/382371.shtml
英文地址,請注明出處:http://en.pswp.cn/news/382371.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vivo C/C++工程師 HR視頻面試問題總結20180807

一開始沒想到這次視頻面是HR面試,還以為是技術面試,畢竟上次面試的時候技術問題問的相對比較少,所以面試準備方向有點兒錯了,不過還是總結一下具體問題。 1)自我介紹:吸取了上次自我介紹的經驗,…

在Redis客戶端設置連接密碼 并演示密碼登錄

我們先連接到Redis服務 然后 我們要輸入 CONFIG SET requirepass “新密碼” 例如 CONFIG SET requirepass "A15167"這樣 密碼就被設置成立 A15167 我們 輸入 AUTH 密碼 例如 AUTH A15167這里 返回OK說明成功了 然后 我們退出在登錄就真的需要 redis-cli -h IP地…

redis源碼剖析(五)—— 字符串,列表,哈希,集合,有序集合

文章目錄對象REDIS_STRING (字符串)REDIS_LIST 列表REDIS_SET (集合)REDIS_ZSET (有序集合)REDIS_HASH (hash表)int refcount(引用計數器)unsigned lru:REDIS_LRU_BITS對象 對于 Re…

函數sscanf小結

1.sscanf用于處理固定格式的字符串&#xff0c;包含在頭文件<cstdio>中&#xff0c;函數原型為&#xff1a; int sscanf(const char *buffer,const char*format,[]argument ]...); 其中:buffer代表著要存儲的數據&#xff0c;format 代表格式控制字符串&#xff0c;arg…

redis源碼剖析(六)—— Redis 數據庫、鍵過期的實現

文章目錄數據庫的實現數據庫讀寫操作鍵的過期實現數據庫的實現 我們先看代碼 server.h/redisServer struct redisServer{...//保存 db 的數組redisDb *db;//db 的數量int dbnum;... }再看redisDb的代碼&#xff1a; typedef struct redisDb {dict *dict; /*…

多益網絡 視頻面試面試總結20180816

1.首先是自我介紹&#xff1a;因為等了半個小時&#xff0c;所以有點兒緊張&#xff0c;只說了一下自己的學校&#xff0c;愛好和興趣&#xff1b; 2.介紹了一個自己的最成功的項目&#xff1a;我介紹了一個關于GPS導航的項目&#xff0c;介紹了項目的內容和項目的一些工作&am…

redis源碼剖析(七)—— Redis 數據結構dict.c

文章目錄dict.hdict.cdict.h //定義錯誤相關的碼 #define DICT_OK 0 #define DICT_ERR 1//實際存放數據的地方 typedef struct dictEntry {void *key;void *val;struct dictEntry *next; } dictEntry;//哈希表的定義 typedef struct dict {//指向實際的哈希表記錄(用數組開鏈的…

簡述linux中動態庫和靜態庫的制作調用流程

假設現在有這些文件&#xff1a;sub.c add.c div.c mul.c mainc head.h&#xff08;前4個.C文件的頭文件&#xff09; 1.靜態庫制作流程 gcc -c sub.c add.c div.c mul.c -->生成 .o目標文件文件 ar rcs libmycal.a *.o …

redis源碼剖析(八)—— 當你啟動Redis的時候,Redis做了什么

文章目錄啟動過程初始化server結構體main函數會調用initServer函數初始化服務器狀態載入持久化文件&#xff0c;還原數據庫開始監聽事件流程圖啟動過程 初始化server結構體從配置文件夾在加載參數初始化服務器載入持久化文件開始監聽事件 初始化server結構體 服務器的運行ID…

linux中錯誤總結歸納

1.使用gcc編譯C文件&#xff0c;C文件在for循環語句中出現變量定義 編譯器提示錯誤&#xff1a;“for”loop initial declarations are only allowed in C99 mode. note:use option -stdc99or-stdgnu99 to compile; 原因&#xff1a;gcc的標準是基于c89的&#xff0c;c89不能在…

redis源碼剖析(十一)—— Redis字符串相關函數實現

文章目錄初始化字符串字符串基本操作字符串拼接操作other獲取指定范圍里的字符串將字符串中的所有字符均轉為小寫的形式將字符串中所有字符均轉為大寫的形式字符串比較other#define SDS_ABORT_ON_OOM#include "sds.h" #include <stdio.h> #include <stdlib.…

makefile內容小結

makefile中每個功能主要分為三部分&#xff1a;目標&#xff0c;依賴條件和命令語句 1.支持對比更新的Makefile寫法&#xff08;只會編譯文件時.o文件和.c文件時間不一致的文件&#xff09; 2.使用makefile自動變量和自定義變量的makefile寫法 其中&#xff1a;這三個符號為ma…

事務隔離級別動圖演示

事務的基本要素&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09; 事務開始后所有操作&#xff0c;要么全部做完&#xff0c;要么全部不做&#xff0c;不可能停滯在中間環節。事務執行過程中出錯&#xff0c;會回滾到事務開始前的狀態&#xff0c;所有的…

C/C++的優點和缺點

1.C/C語言的優點 C語言是面向過程的語言&#xff0c;常用來編寫操作系統。C語言是從C語言發展過來的&#xff0c;是一門面向對象的語言&#xff0c;它繼承了C語言的優勢&#xff0c;同時也添加了三個主要的內容&#xff1a;Oriented-Object class,Template,STL. 1)C/C可以潛入…

C/C++命令行參數那點事

int main(int argc, char *argv[ ]); 1.命令行參數&#xff1a;在命令行中給定的參數&#xff1b; 2.命令行參數在對函數main的調用時&#xff0c;主要有兩個參數送到main,一個是argc(argument count),命令行參數的個數&#xff0c;另外一個是argv,命令行參數的數組,命令行參…

mysql row_id為什么是6字節?為什么是8字節

mysql row_id是幾個字節&#xff1f; row_id InnoDB表中在沒有默認主鍵的情況下會生成一個6字節空間的自動增長主鍵 row_id是整型還是字符型&#xff1f; 源代碼中 row_id 是 ib_uint64_t 這是 8字節 uint64_t 是整形 為什么是6個字節&#xff1f; P.S. Base64編碼說明 B…

linux中的man文檔結構

使用命令 man chapter章節號查找的內容

偽隨機數和真隨機數

偽隨機數小項目 猜數字游戲 //C語言 猜數字游戲 https://blog.csdn.net/csdn_kou/article/details/79785709 C語言之隨機數生成超詳解 https://blog.csdn.net/csdn_kou/article/details/79788815 在上面的文章中&#xff0c;使用固定函數就一直是生成固定的隨機結果&#…

linux中的IO函數

1)open函數&#xff1a;以特定的方式打開一個文件&#xff1b; 頭文件&#xff1a;sys/type.h sys/stat.h fcntl.h 返回值&#xff1a;錯誤則返回-1&#xff0c;正確則返回文件描述符&#xff08;int類型&#xff0c;范圍為3~1023,文件的標號&#xff09; 函數原型&#xff…

ps -ef和ps aux

ps -ef和ps aux ps -ef unix風格 -e 列出所有進程 -f 完整格式 UID PID PPID C STIME TTY TIME CMD root 1 0 0 8月27 ? 00:25:08 /usr/lib/systemd/systemd --switched-root --system --deserialize 22 root 2 0 0 8月…