深入了解Redis底層數(shù)據(jù)結構

說明

說到Redis的數(shù)據(jù)結構,我們大概會很快想到Redis的5種常見數(shù)據(jù)結構:字符串(String)来吩、列表(List)、散列(Hash)蔽莱、集合(Set)弟疆、有序集合(Sorted Set),以及他們的特點和運用場景盗冷。不過它們是Redis對外暴露的數(shù)據(jù)結構怠苔,用于API的操作,而組成它們的底層基礎數(shù)據(jù)結構又是什么呢

  • 簡單動態(tài)字符串(SDS)
  • 鏈表
  • 字典
  • 跳躍表
  • 整數(shù)集合
  • 壓縮列表

Redis的GitHub地址https://github.com/antirez/redis

簡單動態(tài)字符串(SDS)

Redis是用C語言寫的仪糖,但是Redis并沒有使用C的字符串表示(C是字符串是以\0空字符結尾的字符數(shù)組)柑司,而是自己構建了一種簡單動態(tài)字符串(simple dynamic string,SDS)的抽象類型锅劝,并作為Redis的默認字符串表示

在Redis中攒驰,包含字符串值的鍵值對底層都是用SDS實現(xiàn)的

SDS的定義

SDS的結構定義在sds.h文件中,SDS的定義在Redis 3.2版本之后有一些改變故爵,由一種數(shù)據(jù)結構變成了5種數(shù)據(jù)結構讼育,會根據(jù)SDS存儲的內容長度來選擇不同的結構,以達到節(jié)省內存的效果稠集,具體的結構定義奶段,我們看以下代碼

// 3.0
struct sdshdr {
    // 記錄buf數(shù)組中已使用字節(jié)的數(shù)量,即SDS所保存字符串的長度
    unsigned int len;
    // 記錄buf數(shù)據(jù)中未使用的字節(jié)數(shù)量
    unsigned int free;
    // 字節(jié)數(shù)組剥纷,用于保存字符串
    char buf[];
};

// 3.2
/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

3.2版本之后痹籍,會根據(jù)字符串的長度來選擇對應的數(shù)據(jù)結構

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)  // 32
        return SDS_TYPE_5;
    if (string_size < 1<<8)  // 256
        return SDS_TYPE_8;
    if (string_size < 1<<16)   // 65536 64k
        return SDS_TYPE_16;
    if (string_size < 1ll<<32)  // 4294967296 4G
        return SDS_TYPE_32;
    return SDS_TYPE_64;
}

下面以3.2版本的sdshdr8看一個示例

  • len:記錄當前已使用的字節(jié)數(shù)(不包括'\0'),獲取SDS長度的復雜度為O(1)
  • alloc:記錄當前字節(jié)數(shù)組總共分配的字節(jié)數(shù)量(不包括'\0'
  • flags:標記當前字節(jié)數(shù)組的屬性晦鞋,是sdshdr8還是sdshdr16等蹲缠,flags值的定義可以看下面代碼
  • buf:字節(jié)數(shù)組棺克,用于保存字符串,包括結尾空白字符'\0'
// flags值定義
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

上面的字節(jié)數(shù)組的空白處表示未使用空間线定,是Redis優(yōu)化的空間策略娜谊,給字符串的操作留有余地,保證安全提高效率

SDS與C字符串的區(qū)別

C語言使用長度為N+1的字符數(shù)組來表示長度為N的字符串斤讥,字符數(shù)組的最后一個元素為空字符'\0'纱皆,但是這種簡單的字符串表示方法并不能滿足Redis對于字符串在安全性、效率以及功能方面的要求芭商,那么使用SDS派草,會有哪些好處呢

參考于《Redis設計與實現(xiàn)》

常數(shù)復雜度獲取字符串長度

C字符串不記錄字符串長度,獲取長度必須遍歷整個字符串铛楣,復雜度為O(N)近迁;而SDS結構中本身就有記錄字符串長度的len屬性,所有復雜度為O(1)簸州。Redis將獲取字符串長度所需的復雜度從O(N)降到了O(1)鉴竭,確保獲取字符串長度的工作不會成為Redis的性能瓶頸

杜絕緩沖區(qū)溢出,減少修改字符串時帶來的內存重分配次數(shù)

C字符串不記錄自身的長度岸浑,每次增長或縮短一個字符串搏存,都要對底層的字符數(shù)組進行一次內存重分配操作。如果是拼接append操作之前沒有通過內存重分配來擴展底層數(shù)據(jù)的空間大小助琐,就會產(chǎn)生緩存區(qū)溢出祭埂;如果是截斷trim操作之后沒有通過內存重分配來釋放不再使用的空間面氓,就會產(chǎn)生內存泄漏

而SDS通過未使用空間解除了字符串長度和底層數(shù)據(jù)長度的關聯(lián)兵钮,3.0版本是用free屬性記錄未使用空間,3.2版本則是alloc屬性記錄總的分配字節(jié)數(shù)量舌界。通過未使用空間掘譬,SDS實現(xiàn)了空間預分配惰性空間釋放兩種優(yōu)化的空間分配策略,解決了字符串拼接和截取的空間問題

二進制安全

C字符串中的字符必須符合某種編碼呻拌,除了字符串的末尾葱轩,字符串里面是不能包含空字符的,否則會被認為是字符串結尾藐握,這些限制了C字符串只能保存文本數(shù)據(jù)靴拱,而不能保存像圖片這樣的二進制數(shù)據(jù)

而SDS的API都會以處理二進制的方式來處理存放在buf數(shù)組里的數(shù)據(jù),不會對里面的數(shù)據(jù)做任何的限制猾普。SDS使用len屬性的值來判斷字符串是否結束袜炕,而不是空字符

兼容部分C字符串函數(shù)

雖然SDS的API是二進制安全的,但還是像C字符串一樣以空字符結尾初家,目的是為了讓保存文本數(shù)據(jù)的SDS可以重用一部分C字符串的函數(shù)

C字符串與SDS對比

C字符串 SDS
獲取字符串長度復雜度為O(N) 獲取字符串長度復雜度為O(1)
API是不安全的偎窘,可能會造成緩沖區(qū)溢出 API是安全的乌助,不會造成緩沖區(qū)溢出
修改字符串長度必然會需要執(zhí)行內存重分配 修改字符串長度N次最多會需要執(zhí)行N次內存重分配
只能保存文本數(shù)據(jù) 可以保存文本或二進制數(shù)據(jù)
可以使用所有<string.h>庫中的函數(shù) 可以使用一部分<string.h>庫中的函數(shù)

鏈表

鏈表是一種比較常見的數(shù)據(jù)結構了,特點是易于插入和刪除陌知、內存利用率高他托、且可以靈活調整鏈表長度,但隨機訪問困難仆葡。許多高級編程語言都內置了鏈表的實現(xiàn)赏参,但是C語言并沒有實現(xiàn)鏈表,所以Redis實現(xiàn)了自己的鏈表數(shù)據(jù)結構

鏈表在Redis中應用的非常廣浙芙,列表(List)的底層實現(xiàn)就是鏈表登刺。此外,Redis的發(fā)布與訂閱嗡呼、慢查詢纸俭、監(jiān)視器等功能也用到了鏈表

鏈表節(jié)點和鏈表的定義

鏈表上的節(jié)點定義如下,adlist.h/listNode

typedef struct listNode {
    // 前置節(jié)點
    struct listNode *prev;
    // 后置節(jié)點
    struct listNode *next;
    // 節(jié)點值
    void *value;
} listNode;

鏈表的定義如下南窗,adlist.h/list

typedef struct list {
    // 鏈表頭節(jié)點
    listNode *head;
    // 鏈表尾節(jié)點
    listNode *tail;
    // 節(jié)點值復制函數(shù)
    void *(*dup)(void *ptr);
    // 節(jié)點值釋放函數(shù)
    void (*free)(void *ptr);
    // 節(jié)點值對比函數(shù)
    int (*match)(void *ptr, void *key);
    // 鏈表所包含的節(jié)點數(shù)量
    unsigned long len;
} list;

每個節(jié)點listNode可以通過prevnext指針分布指向前一個節(jié)點和后一個節(jié)點組成雙端鏈表揍很,同時每個鏈表還會有一個list結構為鏈表提供表頭指針head、表尾指針tail万伤、以及鏈表長度計數(shù)器len窒悔,還有三個用于實現(xiàn)多態(tài)鏈表的類型特定函數(shù)

  • dup:用于復制鏈表節(jié)點所保存的值
  • free:用于釋放鏈表節(jié)點所保存的值
  • match:用于對比鏈表節(jié)點所保存的值和另一個輸入值是否相等

鏈表結構圖

鏈表特性

  • 雙端鏈表:帶有指向前置節(jié)點和后置節(jié)點的指針,獲取這兩個節(jié)點的復雜度為O(1)
  • 無環(huán):表頭節(jié)點的prev和表尾節(jié)點的next都指向NULL敌买,對鏈表的訪問以NULL結束
  • 鏈表長度計數(shù)器:帶有len屬性简珠,獲取鏈表長度的復雜度為O(1)
  • 多態(tài):鏈表節(jié)點使用 void*指針保存節(jié)點值,可以保存不同類型的值

字典

字典虹钮,又稱為符號表(symbol table)聋庵、關聯(lián)數(shù)組(associative array)或映射(map),是一種用于保存鍵值對(key-value pair)的抽象數(shù)據(jù)結構芙粱。字典中的每一個鍵都是唯一的祭玉,可以通過鍵查找與之關聯(lián)的值,并對其修改或刪除

Redis的鍵值對存儲就是用字典實現(xiàn)的春畔,散列(Hash)的底層實現(xiàn)之一也是字典

我們直接來看一下字典是如何定義和實現(xiàn)的吧

字典的定義實現(xiàn)

Redis的字典底層是使用哈希表實現(xiàn)的脱货,一個哈希表里面可以有多個哈希表節(jié)點,每個哈希表節(jié)點中保存了字典中的一個鍵值對

哈希表結構定義律姨,dict.h/dictht

typedef struct dictht {
    // 哈希表數(shù)組
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩碼振峻,用于計算索引值,等于size-1
    unsigned long sizemask;
    // 哈希表已有節(jié)點的數(shù)量
    unsigned long used;
} dictht;

哈希表是由數(shù)組table組成择份,table中每個元素都是指向dict.h/dictEntry結構的指針扣孟,哈希表節(jié)點的定義如下

typedef struct dictEntry {
    // 鍵
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    // 指向下一個哈希表節(jié)點,形成鏈表
    struct dictEntry *next;
} dictEntry;

其中key是我們的鍵缓淹;v是鍵值哈打,可以是一個指針塔逃,也可以是整數(shù)或浮點數(shù);next屬性是指向下一個哈希表節(jié)點的指針料仗,可以讓多個哈希值相同的鍵值對形成鏈表湾盗,解決鍵沖突問題

最后就是我們的字典結構,dict.h/dict

typedef struct dict {
    // 和類型相關的處理函數(shù)
    dictType *type;
    // 私有數(shù)據(jù)
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引立轧,當rehash不再進行時格粪,值為-1
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    // 迭代器數(shù)量
    unsigned long iterators; /* number of iterators currently running */
} dict;

type屬性和privdata屬性是針對不同類型的鍵值對,用于創(chuàng)建多類型的字典氛改,type是指向dictType結構的指針帐萎,privdata則保存需要傳給類型特定函數(shù)的可選參數(shù),關于dictType結構和類型特定函數(shù)可以看下面代碼

typedef struct dictType {
    // 計算哈希值的行數(shù)
    uint64_t (*hashFunction)(const void *key);
    // 復制鍵的函數(shù)
    void *(*keyDup)(void *privdata, const void *key);
    // 復制值的函數(shù)
    void *(*valDup)(void *privdata, const void *obj);
    // 對比鍵的函數(shù)
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    // 銷毀鍵的函數(shù)
    void (*keyDestructor)(void *privdata, void *key);
    // 銷毀值的函數(shù)
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

dictht屬性是兩個元素的數(shù)組胜卤,包含兩個dictht哈希表疆导,一般字典只使用ht[0]哈希表,``ht[1]哈希表會在對ht[0]哈希表進行rehash(重哈希)的時候使用葛躏,即當哈希表的鍵值對數(shù)量超過負載數(shù)量過多的時候澈段,會將鍵值對遷移到ht[1]`上

rehashidx也是跟rehash相關的,rehash的操作不是瞬間完成的舰攒,rehashidx記錄著rehash的進度败富,如果目前沒有在進行rehash,它的值為-1

結合上面的幾個結構摩窃,我們來看一下字典的結構圖(沒有在進行rehash)

在這里兽叮,哈希算法和rehash(重新散列)的操作不再詳細說明,有機會以后單獨介紹

當一個新的鍵值對要添加到字典中時猾愿,會根據(jù)鍵值對的鍵計算出哈希值和索引值鹦聪,根據(jù)索引值放到對應的哈希表上,即如果索引值為0匪蟀,則放到ht[0]哈希表上椎麦。當有兩個或多個的鍵分配到了哈希表數(shù)組上的同一個索引時宰僧,就發(fā)生了鍵沖突的問題材彪,哈希表使用鏈地址法來解決,即使用哈希表節(jié)點的next指針琴儿,將同一個索引上的多個節(jié)點連接起來段化。當哈希表的鍵值對太多或太少,就需要對哈希表進行擴展和收縮造成,通過rehash(重新散列)來執(zhí)行

跳躍表

一個普通的單鏈表查詢一個元素的時間復雜度為O(N)显熏,即便該單鏈表是有序的。使用跳躍表(SkipList)是來解決查找問題的晒屎,它是一種有序的數(shù)據(jù)結構喘蟆,不屬于平衡樹結構缓升,也不屬于Hash結構,它通過在每個節(jié)點維持多個指向其他節(jié)點的指針蕴轨,而達到快速訪問節(jié)點的目的

跳躍表是有序集合(Sorted Set)的底層實現(xiàn)之一港谊,如果有序集合包含的元素比較多,或者元素的成員是比較長的字符串時橙弱,Redis會使用跳躍表做有序集合的底層實現(xiàn)

跳躍表的定義

跳躍表其實可以把它理解為多層的鏈表歧寺,它有如下的性質

  • 多層的結構組成,每層是一個有序的鏈表
  • 最底層(level 1)的鏈表包含所有的元素
  • 跳躍表的查找次數(shù)近似于層數(shù)棘脐,時間復雜度為O(logn)斜筐,插入、刪除也為 O(logn)
  • 跳躍表是一種隨機化的數(shù)據(jù)結構(通過拋硬幣來決定層數(shù))

那么如何來理解跳躍表呢蛀缝,我們從最底層的包含所有元素的鏈表開始顷链,給定如下的鏈表

然后我們每隔一個元素,把它放到上一層的鏈表當中屈梁,這里我把它叫做上浮(注意蕴潦,科學的辦法是拋硬幣的方式,來決定元素是否上浮到上一層鏈表俘闯,我這里先簡單每隔一個元素上浮到上一層鏈表潭苞,便于理解),操作完成之后的結構如下

查找元素的方法是這樣真朗,從上層開始查找此疹,大數(shù)向右找到頭,小數(shù)向左找到頭遮婶,例如我要查找17蝗碎,查詢的順序是:13 -> 46 -> 22 -> 17;如果是查找35旗扑,則是 13 -> 46 -> 22 -> 46 -> 35蹦骑;如果是54,則是 13 -> 46 -> 54

上面是查找元素臀防,如果是添加元素眠菇,是通過拋硬幣的方式來決定該元素會出現(xiàn)到多少層,也就是說它會有 1/2的概率出現(xiàn)第二層袱衷、1/4 的概率出現(xiàn)在第三層......

跳躍表節(jié)點的刪除和添加都是不可預測的捎废,很難保證跳表的索引是始終均勻的,拋硬幣的方式可以讓大體上是趨于均勻的

假設我們已經(jīng)有了上述例子的一個跳躍表了致燥,現(xiàn)在往里面添加一個元素18登疗,通過拋硬幣的方式來決定它會出現(xiàn)的層數(shù),是正面就繼續(xù),反面就停止辐益,假如我拋了2次硬幣断傲,第一次為正面,第二次為反面

跳躍表的刪除很簡單智政,只要先找到要刪除的節(jié)點艳悔,然后順藤摸瓜刪除每一層相同的節(jié)點就好了

跳躍表維持結構平衡的成本是比較低的,完全是依靠隨機女仰,相比二叉查找樹猜年,在多次插入刪除后,需要Rebalance來重新調整結構平衡

跳躍表的實現(xiàn)

Redis的跳躍表實現(xiàn)是由redis.h/zskiplistNoderedis.h/zskiplist(3.2版本之后redis.h改為了server.h)兩個結構定義疾忍,zskiplistNode定義跳躍表的節(jié)點乔外,zskiplist保存跳躍表節(jié)點的相關信息

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    // 成員對象 (robj *obj;)
    sds ele;
    // 分值
    double score;
    // 后退指針
    struct zskiplistNode *backward;
    // 層
    struct zskiplistLevel {
        // 前進指針
        struct zskiplistNode *forward;
        // 跨度
        // 跨度實際上是用來計算元素排名(rank)的,在查找某個節(jié)點的過程中一罩,將沿途訪過的所有層的跨度累積起來杨幼,得到的結果就是目標節(jié)點在跳躍表中的排位
        unsigned long span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    // 表頭節(jié)點和表尾節(jié)點
    struct zskiplistNode *header, *tail;
    // 表中節(jié)點的數(shù)量
    unsigned long length;
    // 表中層數(shù)最大的節(jié)點的層數(shù)
    int level;
} zskiplist;

zskiplistNode結構

  • level數(shù)組(層):每次創(chuàng)建一個新的跳表節(jié)點都會根據(jù)冪次定律計算出level數(shù)組的大小,也就是次層的高度聂渊,每一層帶有兩個屬性-前進指針跨度差购,前進指針用于訪問表尾方向的其他指針;跨度用于記錄當前節(jié)點與前進指針所指節(jié)點的距離(指向的為NULL汉嗽,闊度為0)
  • backward(后退指針):指向當前節(jié)點的前一個節(jié)點
  • score(分值):用來排序欲逃,如果分值相同看成員變量在字典序大小排序
  • objele:成員對象是一個指針,指向一個字符串對象饼暑,里面保存著一個sds稳析;在跳表中各個節(jié)點的成員對象必須唯一,分值可以相同

zskiplist結構

  • header弓叛、tail表頭節(jié)點和表尾節(jié)點
  • length表中節(jié)點的數(shù)量
  • level表中層數(shù)最大的節(jié)點的層數(shù)

假設我們現(xiàn)在展示一個跳躍表彰居,有四個節(jié)點,節(jié)點的高度分別是2撰筷、1陈惰、4、3

zskiplist的頭結點不是一個有效的節(jié)點毕籽,它有ZSKIPLIST_MAXLEVEL層(32層)抬闯,每層的forward指向該層跳躍表的第一個節(jié)點,若沒有則為NULL影钉,在Redis中画髓,上面的跳躍表結構如下

  • 每個跳躍表節(jié)點的層數(shù)在1-32之間
  • 一個跳躍表中掘剪,節(jié)點按照分值大小排序平委,多個節(jié)點的分值是可以相同的,相同時夺谁,節(jié)點按成員對象大小排序
  • 每個節(jié)點的成員變量必須是唯一的

整數(shù)集合

整數(shù)集合(intset)是Redis用于保存整數(shù)值的集合抽象數(shù)據(jù)結構廉赔,可以保存類型為int16_t肉微、int32_t、int64_t的整數(shù)值蜡塌,并且保證集合中不會出現(xiàn)重復元素

整數(shù)集合是集合(Set)的底層實現(xiàn)之一碉纳,如果一個集合只包含整數(shù)值元素,且元素數(shù)量不多時馏艾,會使用整數(shù)集合作為底層實現(xiàn)

整數(shù)集合的定義實現(xiàn)

整數(shù)集合的定義為inset.h/inset

typedef struct intset {
    // 編碼方式
    uint32_t encoding;
    // 集合包含的元素數(shù)量
    uint32_t length;
    // 保存元素的數(shù)組
    int8_t contents[];
} intset;
  • contents數(shù)組:整數(shù)集合的每個元素在數(shù)組中按值的大小從小到大排序劳曹,且不包含重復項
  • length記錄整數(shù)集合的元素數(shù)量,即contents數(shù)組長度
  • encoding決定contents數(shù)組的真正類型琅摩,如INTSET_ENC_INT16铁孵、INTSET_ENC_INT32、INTSET_ENC_INT64

整數(shù)集合的升級

當想要添加一個新元素到整數(shù)集合中時房资,并且新元素的類型比整數(shù)集合現(xiàn)有的所有元素的類型都要長蜕劝,整數(shù)集合需要先進行升級(upgrade),才能將新元素添加到整數(shù)集合里面轰异。每次想整數(shù)集合中添加新元素都有可能會引起升級岖沛,每次升級都需要對底層數(shù)組已有的所有元素進行類型轉換

升級添加新元素:

  • 根據(jù)新元素類型,擴展整數(shù)集合底層數(shù)組的空間大小搭独,并為新元素分配空間
  • 把數(shù)組現(xiàn)有的元素都轉換成新元素的類型婴削,并將轉換后的元素放到正確的位置,且要保持數(shù)組的有序性
  • 添加新元素到底層數(shù)組

整數(shù)集合的升級策略可以提升整數(shù)集合的靈活性牙肝,并盡可能的節(jié)約內存

另外馆蠕,整數(shù)集合不支持降級,一旦升級惊奇,編碼就會一直保持升級后的狀態(tài)

壓縮列表

壓縮列表(ziplist)是為了節(jié)約內存而設計的互躬,是由一系列特殊編碼的連續(xù)內存塊組成的順序性(sequential)數(shù)據(jù)結構,一個壓縮列表可以包含多個節(jié)點颂郎,每個節(jié)點可以保存一個字節(jié)數(shù)組或者一個整數(shù)值

壓縮列表是列表(List)和散列(Hash)的底層實現(xiàn)之一吼渡,一個列表只包含少量列表項,并且每個列表項是小整數(shù)值或比較短的字符串乓序,會使用壓縮列表作為底層實現(xiàn)(在3.2版本之后是使用quicklist實現(xiàn))

壓縮列表的構成

一個壓縮列表可以包含多個節(jié)點(entry)寺酪,每個節(jié)點可以保存一個字節(jié)數(shù)組或者一個整數(shù)值

各部分組成說明如下

  • zlbytes:記錄整個壓縮列表占用的內存字節(jié)數(shù),在壓縮列表內存重分配替劈,或者計算zlend的位置時使用
  • zltail:記錄壓縮列表表尾節(jié)點距離壓縮列表的起始地址有多少字節(jié)寄雀,通過該偏移量,可以不用遍歷整個壓縮列表就可以確定表尾節(jié)點的地址
  • zllen:記錄壓縮列表包含的節(jié)點數(shù)量陨献,但該屬性值小于UINT16_MAX(65535)時盒犹,該值就是壓縮列表的節(jié)點數(shù)量,否則需要遍歷整個壓縮列表才能計算出真實的節(jié)點數(shù)量
  • entryX:壓縮列表的節(jié)點
  • zlend:特殊值0xFF(十進制255),用于標記壓縮列表的末端

壓縮列表節(jié)點的構成

每個壓縮列表節(jié)點可以保存一個字節(jié)數(shù)字或者一個整數(shù)值急膀,結構如下

  • previous_entry_ength:記錄壓縮列表前一個字節(jié)的長度
  • encoding:節(jié)點的encoding保存的是節(jié)點的content的內容類型
  • content:content區(qū)域用于保存節(jié)點的內容沮协,節(jié)點內容類型和長度由encoding決定

對象

上面介紹了Redis的主要底層數(shù)據(jù)結構,包括簡單動態(tài)字符串(SDS)卓嫂、鏈表慷暂、字典、跳躍表晨雳、整數(shù)集合行瑞、壓縮列表。但是Redis并沒有直接使用這些數(shù)據(jù)結構來構建鍵值對數(shù)據(jù)庫餐禁,而是基于這些數(shù)據(jù)結構創(chuàng)建了一個對象系統(tǒng)蘑辑,也就是我們所熟知的可API操作的Redis那些數(shù)據(jù)類型,如字符串(String)坠宴、列表(List)洋魂、散列(Hash)、集合(Set)喜鼓、有序集合(Sorted Set)

根據(jù)對象的類型可以判斷一個對象是否可以執(zhí)行給定的命令副砍,也可針對不同的使用場景,對象設置有多種不同的數(shù)據(jù)結構實現(xiàn)庄岖,從而優(yōu)化對象在不同場景下的使用效率

類型 編碼 BOJECT ENCODING 命令輸出 對象
REDIS_STRING REDIS_ENCODING_INT "int" 使用整數(shù)值實現(xiàn)的字符串對象
REDIS_STRING REDIS_ENCODING_EMBSTR "embstr" 使用embstr編碼的簡單動態(tài)字符串實現(xiàn)的字符串對象
REDIS_STRING REDIS_ENCODING_RAW "raw" 使用簡單動態(tài)字符串實現(xiàn)的字符串對象
REDIS_LIST REDIS_ENCODING_ZIPLIST "ziplist" 使用壓縮列表實現(xiàn)的列表對象
REDIS_LIST REDIS_ENCODING_LINKEDLIST '"linkedlist' 使用雙端鏈表實現(xiàn)的列表對象
REDIS_HASH REDIS_ENCODING_ZIPLIST "ziplist" 使用壓縮列表實現(xiàn)的哈希對象
REDIS_HASH REDIS_ENCODING_HT "hashtable" 使用字典實現(xiàn)的哈希對象
REDIS_SET REDIS_ENCODING_INTSET "intset" 使用整數(shù)集合實現(xiàn)的集合對象
REDIS_SET REDIS_ENCODING_HT "hashtable" 使用字典實現(xiàn)的集合對象
REDIS_ZSET REDIS_ENCODING_ZIPLIST "ziplist" 使用壓縮列表實現(xiàn)的有序集合對象
REDIS_ZSET REDIS_ENCODING_SKIPLIST "skiplist" 使用跳躍表表實現(xiàn)的有序集合對象

參考:《Redis設計與實現(xiàn)》

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末豁翎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子隅忿,更是在濱河造成了極大的恐慌心剥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件背桐,死亡現(xiàn)場離奇詭異优烧,居然都是意外死亡,警方通過查閱死者的電腦和手機链峭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門畦娄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弊仪,你說我怎么就攤上這事熙卡。” “怎么了励饵?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵驳癌,是天一觀的道長。 經(jīng)常有香客問我役听,道長颓鲜,這世上最難降的妖魔是什么表窘? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮灾杰,結果婚禮上蚊丐,老公的妹妹穿的比我還像新娘熙参。我一直安慰自己艳吠,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布孽椰。 她就那樣靜靜地躺著昭娩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黍匾。 梳的紋絲不亂的頭發(fā)上栏渺,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音锐涯,去河邊找鬼磕诊。 笑死,一個胖子當著我的面吹牛纹腌,可吹牛的內容都是我干的霎终。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼升薯,長吁一口氣:“原來是場噩夢啊……” “哼莱褒!你這毒婦竟也來了?” 一聲冷哼從身側響起涎劈,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤广凸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蛛枚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谅海,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年蹦浦,在試婚紗的時候發(fā)現(xiàn)自己被綠了胁赢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡白筹,死狀恐怖智末,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情徒河,我是刑警寧澤系馆,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站顽照,受9級特大地震影響由蘑,放射性物質發(fā)生泄漏闽寡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一尼酿、第九天 我趴在偏房一處隱蔽的房頂上張望爷狈。 院中可真熱鬧,春花似錦裳擎、人聲如沸涎永。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羡微。三九已至,卻和暖如春惶我,著一層夾襖步出監(jiān)牢的瞬間妈倔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工绸贡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盯蝴,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓听怕,卻偏偏與公主長得像捧挺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叉跛,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • 1.演示數(shù)據(jù)類型的實現(xiàn) 上篇博客我們在介紹 key 相關命令的時候松忍,介紹了如下命令: ``` OBJECT ENC...
    mkdlp閱讀 391評論 0 0
  • Redis 是一個鍵值對數(shù)據(jù)庫(key-value DB),數(shù)據(jù)庫的值可以是字符串筷厘、集合红符、列表等多種類型的對象劲腿,而...
    吳昂_ff2d閱讀 3,097評論 0 5
  • 目錄 1、演示數(shù)據(jù)類型的實現(xiàn) 2、簡單動態(tài)字符串 3揭璃、鏈表 4披蕉、字典 5榛臼、跳躍表 6峻厚、整數(shù)集合 7、壓縮列表 8骤铃、...
    匆匆歲月閱讀 1,172評論 0 28
  • 也許做了個好夢 也許睡了個好覺 也許早晨有個好胃口 也許今天有個約會 也許今天是周末 也許今天有大喜事 也許沒有也...
    陶貝仁者閱讀 503評論 3 18
  • 最近在寫Android拉岁,想做一個關于直播的小程序,數(shù)據(jù)想從斗魚的API上獲取惰爬,求各位大神指點一下 怎么獲取斗魚的直...
    白菜程序閱讀 4,669評論 1 0