redis(2)——redis的數據結構

根據書籍《redis設計與實現》總結。

一、簡單動態字符串

在redis數據庫裏面,包含字符串值的鍵值對在底層都是由SDS實現的。

1、SDS數據結構

struct sdshdr {
      //記錄buf數組中已使用的數量 
      //等於SDS所保存字符串的長度 
      int len; 
      //記錄buf數組中未使用字節的數量 
      int free; 
     //字節數組,用於保存字符串 
     char buf[];
}

特點:

(1)、常數複雜度獲取字符串長度

(2)、杜絕緩衝區溢出

       當SDS API需要對SDS進行修改時,API會先檢查SDS的空間是否滿足修改所需的要求,如果不滿足的話,API會自動將SDS的空間擴展至執行修改所需要的大小,然後在執行實際的修改操作,所以使用SDS既不需要手動修改SDS的空間大小,也不會出現前面所說的緩衝區溢出問題。

3、減少修改字符串是帶來的內存重分配次數

3.1 空間預分配

空間預分配用於優化SDS的字符串增長操作:當SDS的API對一個SDS進行修改,並且需要對SDS進行空間擴展的時候,程序不僅會爲SDS分配修改所必須要的空間,還會爲SDS分配額外的未使用空間。

3.2 惰性預分配

惰性空間釋放用於優化SDS的字符串縮短操作:當SDS的API需要縮短SDS保存的字符串時,程序並不立即使用內存重分配來回收縮短後多出來的字節,而是使用free屬性將這些字節的數量記錄下來,並等待將來使用。

4、二進制安全

SDS API都會以處理二進制的方式來處理SDS存放在buf數組裏面的數據,程序不會對其中的數據做任何限制、過濾、或者假設,數據寫入時是什麼樣的,它被讀取時就是什麼樣。

5、兼容部分C字符串函數

二、鏈表

鏈表節點:

typedef struct listNode { 
   struct listNode *prev;  //前置節點
   struct listNode *next;  //後置節點
   void *value;  //節點的值
} listNode;


鏈表:

/* 雙向鏈表的定義 */  
typedef struct list {  
    listNode *head;  //表頭節點
    listNode *tail;  //表尾節點
    /* 定義三個函數指針 */  
    void *(*dup)(void *ptr);    // 節點值複製函數
    void (*free)(void *ptr);    // 節點值釋放函數
    int (*match)(void *ptr, void *key); // 節點值對比函數  
    unsigned long len;  //鏈表所包含的節點數量
} list;

三、字典

字典,又稱爲符號表、關聯數組或映射,是一種用於保存鍵值對的抽象數據結構。

Redis數據庫就是字典來作爲底層實現的,對數據庫的增、刪、改、查操作也是構建對字典的操作之上的。

字典還是哈希鍵的底層實現之一,當一個哈希鍵包含的鍵值對比較多,又或者鍵值對中的元素都是比較城的字符串時,Redis就會使用字典作爲哈希鍵的底層實現。

1、字典的結構

Redis的字典使用哈希表作爲底層實現,一個哈希表裏面可以有多個哈希表節點,而每個哈希表節點就保存了字典中的一個鍵值對。

/* 哈希表結構 */  
typedef struct dictht {  
    // 散列數組。  
    dictEntry **table;  
    // 散列數組的長度  
    unsigned long size;  
    // 哈希表大小掩碼,用於計算索引值,等於size減1  
    unsigned long sizemask;  
    // 散列數組中已經被使用的節點數量  
    unsigned long used;  
} dictht;
/* 哈希表節點*/  
typedef struct dictEntry {  
    // 關鍵字key定義  
    void *key;    
    // 值value定義,只能存放一個被選中的成員  
    union {  
        void *val;        
        uint64_t u64;     
        int64_t s64;      
        double d;         
    } v;  
    // 指向下一個鍵值對節點,形成鏈表
    struct dictEntry *next;  
} dictEntry;
/* 字典的主操作類,對dictht結構再次包裝  */
typedef struct dict {
    // 字典類型
    dictType *type;
    // 私有數據
    void *privdata;
    // 一個字典中有兩個哈希表
    dictht ht[2];
    // 數據動態遷移的下標位置
    long rehashidx; 
    // 當前正在使用的迭代器的數量
    int iterators; 
} dict;
/* 定義了字典操作的公共方法 */  
typedef struct dictType {  
    /* hash方法,根據關鍵字計算哈希值 */  
    unsigned int (*hashFunction)(const void *key);  
    /* 複製key */  
    void *(*keyDup)(void *privdata, const void *key);  
    /* 複製value */  
    void *(*valDup)(void *privdata, const void *obj);  
    /* 關鍵字比較方法 */  
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);  
    /* 銷燬key */  
    void (*keyDestructor)(void *privdata, void *key);  
    /* 銷燬value */  
    void (*valDestructor)(void *privdata, void *obj);  
} dictType;


2、字典的操作

2.1 哈希算法

當要將一個新的鍵值對添加到字典裏面時,程序需要先根據鍵值對的鍵計算出哈希值和索引值,然後再根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引上面。

2.2 解決鍵衝突

Redis的哈希表使用鏈地址法來解決鍵衝突,每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向鏈表連接起來。

2.3 rehash和漸進式rehash

《redis設計與實現》P29

四、跳躍表

redis的跳躍表結構設計的真心沒看懂。《redis設計與實現》P38

五、整數集合

1、整數集合的數據結構

/* 整數集合結構體 */
typedef struct intset {
    // 編碼方式
    uint32_t encoding;
    // 集合中包含的元素數量
    uint32_t length;
    // 真正保存元素的數組
    int8_t contents[];
} intset;

content數組是整數集合的底層實現:整數集合的每個元素都是content數組的一個數組項,各個項在數組中按值的大小從小到大有序地排列,並且數組中不包含任何重複項。content數組的真正類型取決於encoding屬性的值。

2、升級

升級整數集合並添加新元素共分爲三步進行:

1)根據新元素的類型,擴展整數集合底層數組的空間大小,並未新元素分配空間。

2)將底層數組現有的所有元素都轉換成與新元素相同的類型,並將類型轉換後的元素放置到正確的位上,而且在放置元素的過程中,需要繼續維持底層數組的有序性質不變。

3)將新元素添加到底層數組裏面。

升級的好處:提升靈活性;節約內存

3、降級

整數集合不支持降級操作,一旦對數組進行了升級,編碼就會一直保持升級後的狀態

六、壓縮列表

1、壓縮列表的結構



2、壓縮列表節點的結構


previous_entry_length:記錄了壓縮列表中前一個點的長度。

encoding:記錄了節點的content屬性所保存數據的類型和長度。

content:保存節點的值,節點值可以是一個字節數組或者是整數,值的類型和長度由節點的encoding屬性決定。

3、連鎖更新

《redis設計與實現》P57








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章