redis數據結構和對象一

1. SDS:簡單動態字符串(simple dynamic string)算法

Redis沒有直接使用C語言的字符串,而是本身構建了一種名爲簡單動態字符串類型,並將SDS用做Redis的默認字符串。數組

SDS的定義安全

struct sdshdr {

    // buf 中已佔用空間的長度
    int len;

    // buf 中剩餘可用空間的長度
    int free;

    // 字節數組
    char buf[];
};

SDS與C字符串的區別服務器

  1. SDS獲取字符串長度複雜度爲O(1), C字符串獲取字符串長度複雜度爲O(N);
    由於C字符串獲取字符串並不記錄自身長度,程序必須遍歷整個字符串對每一個字符串計數。這個操做的複雜度爲O(N)
    SDS在len屬性中記錄了SDS自己長度,因此獲取字符串長度複雜度爲O(1)
  2. API是安全的,不會形成緩衝區溢出;
    C字符串不記錄自身長度,若是忘了給字符串擴容執行字符串拼接就會形成溢出
    SDS拼接字符串以前會先經過free字段檢測剩餘空間可否知足需求,不能知足需求的就會擴容。
  3. 減小修改字符串帶來的內存重分配次數;
    C字符串底層老是一個N+1個字符數組,因此每次增加或縮短一個字符串,程序總要對這個C字符串進行一次內存重分配。
    SDS實現空間預分配和惰性空間釋放兩種優化策略.
  4. 二進制安全
    C字符串只能保存文本數據
    SDS能夠保存文本或者二進制數據

** 2. 鏈表 **函數

Redis的List(列表)和發佈訂閱,慢查詢,監視器等功能都用到了鏈表性能

鏈表節點實現 adlist.h/listNode結構表示優化

// listNode 雙端鏈表節點
    typedef struct listNode {

        // 前置節點
        struct listNode *prev;

        // 後置節點
        struct listNode *next;

        // 節點的值
        void *value;

    } listNode;

該鏈表爲雙向鏈表,由多個listNode結點組成的鏈表結構圖以下:ui

鏈表實現 adlist.h/list結構表示
// list 雙端鏈表
    typedef struct list { // 在c語言中,用結構體的方式來模擬對象是一種常見的手法

        // 表頭節點
        listNode *head;

        // 表尾節點
        listNode *tail;

        // 節點值複製函數
        void *(*dup)(void *ptr);

        // 節點值釋放函數
        void(*free)(void *ptr);

        // 節點值對比函數
        int(*match)(void *ptr, void *key);

        // 鏈表所包含的節點數量
        unsigned long len;

    } list;
例:由一個list結構和三個listNode結構組成的鏈表
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227163531303-2083424534.jpg)

鏈表提供表頭指針head,表尾指針tail,以及鏈表長度計數器len,和封裝了3個內置函數
1.dup函數:複製鏈表結點所保存的值
2.free函數:釋放鏈表結點所保存的值
3.match函數:對比鏈表結點所保存的值和另外一個輸入值是否相等
這三個函數是用於實現多態鏈表所需的類型特定函數。

Redis鏈表實現特徵總結
1.雙端:獲取某個結點的前驅和後繼結點都是O(1)
2.無環:表頭的prev指針和表尾的next指針都指向NULL,對鏈表的訪問都是以NULL爲終點
3.帶表頭指針和表尾指針:獲取表頭和表尾的複雜度都是O(1)
4.帶鏈表長度計數器:len屬性記錄,獲取鏈表長度O(1)
5.多態:鏈表結點使用void*指針來保存結點的值,而且能夠經過鏈表結構的三個函數爲結點值設置類型特定函數,因此鏈表能夠保存各類不一樣類型的值指針

  1. 字典code

    1. 字典的實現

    哈希節點使用dictEntry結構表示,每一個dictEntry結構都保存着一個鍵值對。

// dictEntry 哈希表節點
    typedef struct dictEntry {
        // 鍵
        void *key;

        // 值
        union {//值v的類型能夠是如下三種類型
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;

        // 指向下個哈希表節點,造成鏈表
        struct dictEntry *next;

    } dictEntry;
Redis字典使用哈希表有dictht.h/dictht結構定義
typedef struct dictht { 
        // 哈希表數組, 每一個元素都是一條鏈表
        dictEntry **table;

        // 哈希表大小
        unsigned long size;

        // 哈希表大小掩碼,用於計算索引值
        // 老是等於 size - 1
        unsigned long sizemask;

        // 該哈希表已有節點的數量
        unsigned long used;

    } dictht;
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227171229827-765330912.png)
// dict 字典
    typedef struct dict {
        
        // 類型特定函數
        dictType *type; // type裏面主要記錄了一系列的函數,能夠說是規定了一系列的接口

        // 私有數據
        void *privdata; // privdata保存了須要傳遞給那些類型特定函數的可選參數

        //兩張哈希表
        dictht ht[2];//便於漸進式rehash

        //rehash 索引,並無rehash時,值爲 -1
        int rehashidx;
        
        //目前正在運行的安全迭代器的數量
        int iterators;

    } dict;
* type 屬性是一個指向dictType結構的指針,每一個dictType結構保存了一族用於操做特定類型鍵值對的函數,Redis爲用途不一樣的字典設置不一樣的類型特定函數。
* privdata 屬性則保存了須要傳遞給那些類型特定函數的可選參數。
* ht是一個包含兩個項的數組,數組每一個項都是一個dictht哈希表,通常狀況下只使用ht[0]哈希表,ht[1]只會對ht[0]哈希表進行rehash時使用。
* rehashidx它記錄了rehash目前的進度,若是目前沒有進行rehash,那麼他的值爲-1.
// dictType 用於操做字典類型函數
    typedef struct dictType {

        // 計算哈希值的函數
        unsigned int(*hashFunction)(const void *key);

        // 複製鍵的函數
        void *(*keyDup)(void *privdata, const void *key);

        // 複製值的函數
        void *(*valDup)(void *privdata, const void *obj);

        // 對比鍵的函數
        int(*keyCompare)(void *privdata, const void *key1, const void *key2);

        // 銷燬鍵的函數
        void(*keyDestructor)(void *privdata, void *key);

        // 銷燬值的函數
        void(*valDestructor)(void *privdata, void *obj);

    } dictType;
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227171242872-913327145.png)
  1. 哈希算法
    使用字典類型設置的哈希函數擊視鍵key的哈希值
    int hash = dict->type->hashFunction(key)
    使用哈希表的sizemask的屬性和哈希值計算出索引值
    index = hash & dict->ht[0].sizemask;
    使用哈希表節點next指針構成單向鏈表解決哈希衝突。

  2. 擴展和收縮哈希表的恭祝經過執行rehash操做來完成步驟以下
    若是執行的是擴展操做,那麼擴展ht[1]的大小爲第一個大於等於ht[0].used*2的2的n此冪
    若是執行的是收縮操做,那麼收縮ht[1]的大小爲第一個大於等於ht[0].used的2的n此冪
    將保存在ht[0]中的全部鍵值對rehash到ht[1]上面:rehash指的是從新計算鍵的哈希值和索引值,而後將鍵值對放置到ht[1]哈希表的指點位置。
    當ht[0]包含的全部鍵值對都遷移到ht[1]以後,釋放ht[0],將ht[1]設置爲ht[0],並在ht[1]從新建立一個空哈希表,爲下一次rehash作準備。
    當如下條件中的任意一個被知足時,程序會自動開始對哈希表進行擴展操做
    1)服務器目前沒有執行BGSAVE命令或者BGREWRITEOF命令,而且哈希表的負載因子大於等於1.
    2)服務器目前正在執行BGSAVE命令或者BGREWRITEOF命令,而且哈希表的負載因子大於等於5.
    3) 哈希表的負載因子能夠經過公式:load_factor = ht[0].used / ht[0].size;
    4) 哈希表的負載因子小於0.1時,自動執行哈希表收縮操做;

  3. 若是哈希表中有成千上萬個鍵值對,那麼要一次性rehash到ht[1]的話,可能會致使服務器一段時間內中止服務。爲了不rehash對服務器性能影響,服務器二十分屢次,漸進性的將ht[0]裏面的鍵值對漸進性的rehash。詳細步驟: 1)爲ht[1]分配空間,讓字典同時持有ht[0]和ht[1]兩個哈希表。 2)在字典中維持一個索引計數器變量rehashidx,並將它的值設置爲0,表示rehash工做正式開始。 3) 在rehash進行期間,每次對字典執行添加,刪除,查找或者更新操做時,程序除了執行指定的操做外,還會順帶將ht[0]哈希表在rehashidx索引上的全部鍵值對rehash到ht[1],完成順帶操做後,程序將rehashidx屬性的值加一. 4) 隨着字典操做的不斷執行,最終在某個時間點,ht[0]的全部鍵值對會被rehash至ht[1]。這是將rehashidx設置爲-1.表示rehash操做已執行完。

相關文章
相關標籤/搜索