redis 數(shù)據(jù)結(jié)構(gòu)

String

數(shù)據(jù)結(jié)構(gòu)

 struct sdshdr {
   int len;//記錄buf數(shù)組中已使用字節(jié)的數(shù)量等于SDS所保存字符串的長(zhǎng)度
   int alloc;//記錄對(duì)象分配的內(nèi)存空間
   char flag;//記錄字符串類(lèi)型钞钙。
   char buf[];//字節(jié)數(shù)組伍俘,用于保存字符串
}

示例

string示例.png

這里就可以存儲(chǔ)"Redis C",而C只能讀取Redis字符串

對(duì)C字符串和SDS之間的區(qū)別進(jìn)行總結(jié)

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

鏈表

數(shù)據(jù)結(jié)構(gòu)

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

typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

typedef struct list {
    listNode *head; //表頭節(jié)點(diǎn)
    listNode *tail; //表尾節(jié)點(diǎn)
    void *(*dup)(void *ptr); //節(jié)點(diǎn)值復(fù)制函數(shù)
    void (*free)(void *ptr); //節(jié)點(diǎn)值釋放函數(shù)
    int (*match)(void *ptr, void *key); //節(jié)點(diǎn)值對(duì)比函數(shù)
    unsigned long len;
} list;

示例

image.png

鏈表的特點(diǎn)

  • 鏈表唄廣泛用于實(shí)現(xiàn)Redis的個(gè)鐘功能,比如列表鍵更扁,發(fā)布與訂閱盖腕、慢查詢(xún)、監(jiān)視器等浓镜。
  • Redis的鏈表是雙向無(wú)環(huán)鏈表溃列。
  • listIter的結(jié)構(gòu)是為了遍歷list使用的。與java Iterator類(lèi)似
  • list定義的三個(gè)函數(shù)是為了抽象node的value使用竖哩。value的類(lèi)型不同哭廉,三個(gè)函數(shù)的內(nèi)容也不同,類(lèi)似于JAVA的抽象方法相叁。

MAP

數(shù)據(jù)結(jié)構(gòu)

typedef struct dictEntry {
    void *key;  //鍵
    union {  //值
        void *val;  //字符串或別的值
        uint64_t u64;  //無(wú)符號(hào)數(shù)字
        int64_t s64;  //有符號(hào)數(shù)字
        double d; //浮點(diǎn)
    } v;
    struct dictEntry *next;  //指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
} dictEntry;
typedef struct dictht {
    dictEntry **table; //哈希表數(shù)組
    unsigned long size;  //哈希表大小
    unsigned long sizemask;   //哈希表大小掩碼遵绰,用于計(jì)算索引
    unsigned long used;  //該哈希表已有節(jié)點(diǎn)的數(shù)量
} dictht;
typedef struct dict {
    dictType *type;  //類(lèi)型特定函數(shù)
    void *privdata;  //私有數(shù)據(jù)
    dictht ht[2];  //哈希表
    long rehashidx; //rehash索引,當(dāng)rehash不在進(jìn)行時(shí),值為-1
    unsigned long iterators;  //當(dāng)前iterators遍歷的數(shù)量
} dict;

結(jié)構(gòu)示例

image.png

map特色

  • rehash
  1. 為字典的ht[1]哈希表分配空間,,這個(gè)哈希表的空間大小取決于要執(zhí)行的操作,以及ht[0]當(dāng)前包含的鍵值對(duì)數(shù)量(也即使ht[0].used屬性的值):
*  如果執(zhí)行的是擴(kuò)展操作,那么ht[1]的大小為第一個(gè)大于等于ht[0].used*2的2的n次方冪;
*  如果執(zhí)行的是收縮操作,那么ht[1]的大小為第一個(gè)大于等于ht[0].used的2的n次冪.
  1. 將保存在ht[0]中的所有鍵值對(duì)rehash到ht[1]上面;
    3.當(dāng)ht[0]包含的所有鍵值對(duì)都遷移到ht[1]之后(ht[0]變?yōu)榭毡?,釋放ht[0],將ht[1]設(shè)置為ht[0],并在ht[1]新創(chuàng)建一個(gè)空白哈希表,為下一次rehash做準(zhǔn)備.
  • rehash的擴(kuò)展與收縮條件
  1. 服務(wù)器當(dāng)前沒(méi)有執(zhí)行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的負(fù)載因子大于等于1.
  2. 服務(wù)器目前正在執(zhí)行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表負(fù)載因子大于等于5.
  3. 哈希表的負(fù)載因子小于0.1時(shí),程序自動(dòng)開(kāi)始對(duì)哈希表執(zhí)行收縮操作.
    負(fù)載因子公式: load_factor = ht[0].used / ht[0].size;
    java-hashMap負(fù)載因子為0.75 轉(zhuǎn)換為redis的負(fù)載因子為 0.75;
  • 漸進(jìn)式rehash
    如果 ht[0] 里只保存著四個(gè)鍵值對(duì), 那么服務(wù)器可以在瞬間就將這些鍵值對(duì)全部 rehash 到 ht[1] 增淹; 但是椿访, 如果哈希表里保存的鍵值對(duì)數(shù)量不是四個(gè), 而是四百萬(wàn)虑润、四千萬(wàn)甚至四億個(gè)鍵值對(duì)成玫, 那么要一次性將這些鍵值對(duì)全部 rehash 到 ht[1] 的話(huà), 龐大的計(jì)算量可能會(huì)導(dǎo)致服務(wù)器在一段時(shí)間內(nèi)停止服務(wù)拳喻。
    因此哭当, 為了避免 rehash 對(duì)服務(wù)器性能造成影響, 服務(wù)器不是一次性將 ht[0] 里面的所有鍵值對(duì)全部 rehash 到 ht[1] 冗澈, 而是分多次钦勘、漸進(jìn)式地將 ht[0] 里面的鍵值對(duì)慢慢地 rehash 到 ht[1] 。
    以下是哈希表漸進(jìn)式 rehash 的詳細(xì)步驟:
  1. 為 ht[1] 分配空間亚亲, 讓字典同時(shí)持有 ht[0] 和 ht[1] 兩個(gè)哈希表彻采。
    在字典中維持一個(gè)索引計(jì)數(shù)器變量 rehashidx , 并將它的值設(shè)置為 0 捌归, 表示 rehash 工作正式開(kāi)始肛响。
  2. 在 rehash 進(jìn)行期間,ht[0]只會(huì)執(zhí)行刪除'查找,對(duì)字典執(zhí)行添加只會(huì)作用于ht[1] 惜索, 當(dāng) rehash 工作完成之后特笋, 程序?qū)?rehashidx 屬性的值增一。
  3. 隨著字典操作的不斷執(zhí)行门扇, 最終在某個(gè)時(shí)間點(diǎn)上雹有, ht[0] 的所有鍵值對(duì)都會(huì)被 rehash 至 ht[1] 偿渡, 這時(shí)程序?qū)?rehashidx 屬性的值設(shè)為 -1, 表示 rehash 操作已完成霸奕。

代碼示例

//新增替換的時(shí)候調(diào)用該方法
  dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;
    //如果當(dāng)前正在rehash,則調(diào)用一步rehash,轉(zhuǎn)移一個(gè)table數(shù)組
    if (dictIsRehashing(d)) _dictRehashStep(d);

    /* Get the index of the new element, or -1 if
     * the element already exists. (查找key,如果存在返回-1)*/
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. (分配內(nèi)存,rehash時(shí)插入到ht[1]上面)*/
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields. (設(shè)置key值)*/
    dictSetKey(d, entry, key);
    return entry;
}

跳躍表

數(shù)據(jù)結(jié)構(gòu)

typedef struct zskiplistNode {
    sds ele;  //對(duì)象
    double score;  //分值
    struct zskiplistNode *backward;  //后退指針
    struct zskiplistLevel {  //層
        struct zskiplistNode *forward;  //前進(jìn)指針
        unsigned long span;  //跨度
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;//頭尾節(jié)點(diǎn)
    unsigned long length; //list長(zhǎng)度
    int level; //表中層數(shù)最大的節(jié)點(diǎn)層數(shù)
} zskiplist;

示例

跳表結(jié)構(gòu)示例

如果不懂跳表的數(shù)據(jù)結(jié)構(gòu)可以閱讀一下:http://zhangtielei.com/posts/blog-redis-skiplist.html

跳表特色

  1. 跳躍表示有序集合的底層實(shí)現(xiàn)之一
  2. Redis的跳躍表實(shí)現(xiàn)由zskiplist和zskiplistNode兩個(gè)結(jié)構(gòu)組成,其中zskiplist用于保存跳躍表信息,而zskiplistNode則用于標(biāo)示跳躍表節(jié)點(diǎn).
    3.每個(gè)跳躍表節(jié)點(diǎn)的層高都是1至64之間的隨機(jī)數(shù).
    4.在同一個(gè)跳躍表中,多個(gè)節(jié)點(diǎn)可以包含相同的分值,但每個(gè)節(jié)點(diǎn)的成員對(duì)象可以相同(<<redis設(shè)計(jì)與實(shí)現(xiàn)>>說(shuō)對(duì)象唯一,是因?yàn)樘S表永遠(yuǎn)都是和dict一起使用,所以已經(jīng)過(guò)濾掉了重復(fù)對(duì)象)
    代碼:
//插入代碼
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;
    //校驗(yàn)數(shù)據(jù)
    serverAssert(!isnan(score));
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        /* store rank that is crossed to reach the insert position(查詢(xún)插入位置) */
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
        while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
                    sdscmp(x->level[i].forward->ele,ele) < 0)))//因?yàn)閘evel[0]是前指針,所以在遍歷i=0時(shí)是往前遍歷,找到插入的前一個(gè)指針
        {
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        update[i] = x;
    }
 
    level = zslRandomLevel();//獲取隨機(jī)level
    ...省略算法代碼

    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)  //level[0]就是backward一樣
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    zsl->length++;
    return x;
}
  1. 跳躍表中的節(jié)點(diǎn)按照分值大小進(jìn)行排序,當(dāng)分值相同時(shí),節(jié)點(diǎn)按照成員對(duì)象大小進(jìn)行排序.

整數(shù)集合

數(shù)據(jù)結(jié)構(gòu)

typedef struct intset {
    uint32_t encoding;  //編碼方式INT16,INT32,INT64
    uint32_t length;  //數(shù)量
    int8_t contents[];  //保存元素的數(shù)組
} intset;

示例

image.png

image.png

整數(shù)列表特色

  1. 整數(shù)集合是集合鍵的底層實(shí)現(xiàn)之一
  2. 不同的編碼是為了節(jié)約內(nèi)存,數(shù)字小的時(shí)候會(huì)用INT16的方式
  3. 整數(shù)集合只支持升級(jí)操作,不支持降級(jí)操作
  4. 數(shù)組以有序,無(wú)重復(fù)的方式保存集合
    代碼示例:
//插入代碼
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 1;


    if (valenc > intrev32ifbe(is->encoding)) {
        //升級(jí)編碼長(zhǎng)度
        return intsetUpgradeAndAdd(is,value);
    } else {
        //查詢(xún)value是否存在返回1,不存在返回0 pos返回前一個(gè)位置
        if (intsetSearch(is,value,&pos)) {
            if (success) *success = 0;
            return is;
        }

        is = intsetResize(is,intrev32ifbe(is->length)+1);//擴(kuò)容,刪除也會(huì)清空內(nèi)存
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);//不是最后一個(gè)移動(dòng)位置
    }

    _intsetSet(is,pos,value);//設(shè)置值
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

壓縮列表

數(shù)據(jù)結(jié)構(gòu)

壓縮列表沒(méi)有數(shù)據(jù)結(jié)構(gòu),只有一個(gè)大的內(nèi)存快,通過(guò)內(nèi)存地址計(jì)算各個(gè)屬性
組成部分視圖:



代碼實(shí)現(xiàn):

#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))//定義ziplist頭部分
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl))) //記錄zip的大小
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) //記錄zip尾節(jié)點(diǎn)距離壓縮列表起始地址有多少字節(jié),定位尾節(jié)點(diǎn)地址
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))//節(jié)點(diǎn)的長(zhǎng)度

entry的數(shù)據(jù)結(jié)構(gòu):

typedef struct zlentry {
    unsigned int prevrawlensize; //記錄了前一個(gè)節(jié)點(diǎn)的長(zhǎng)度溜宽,通過(guò)這個(gè)值,可以進(jìn)行指針計(jì)算质帅,從而跳轉(zhuǎn)到上一個(gè)節(jié)點(diǎn)适揉。
    unsigned int prevrawlen;     // 前一個(gè)entry長(zhǎng)度
    unsigned int lensize;        // entry的字節(jié)大小
    unsigned int len;           
    unsigned int headersize;     /* prevrawlensize + lensize. */
    unsigned char encoding;      //節(jié)點(diǎn)類(lèi)型可能是整數(shù)或者字符串
    unsigned char *p;            /* Pointer to the very start of the entry, that
                                    is, this points to prev-entry-len field. */
} zlentry;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市煤惩,隨后出現(xiàn)的幾起案子嫉嘀,更是在濱河造成了極大的恐慌,老刑警劉巖魄揉,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剪侮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡洛退,警方通過(guò)查閱死者的電腦和手機(jī)瓣俯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兵怯,“玉大人彩匕,你說(shuō)我怎么就攤上這事∶角” “怎么了驼仪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)袜漩。 經(jīng)常有香客問(wèn)我绪爸,道長(zhǎng),這世上最難降的妖魔是什么宙攻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任毡泻,我火速辦了婚禮,結(jié)果婚禮上粘优,老公的妹妹穿的比我還像新娘。我一直安慰自己呻顽,他們只是感情好雹顺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著廊遍,像睡著了一般嬉愧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喉前,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天没酣,我揣著相機(jī)與錄音王财,去河邊找鬼。 笑死裕便,一個(gè)胖子當(dāng)著我的面吹牛绒净,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播偿衰,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼挂疆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了下翎?” 一聲冷哼從身側(cè)響起缤言,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎视事,沒(méi)想到半個(gè)月后胆萧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俐东,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年跌穗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犬性。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞻离,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乒裆,到底是詐尸還是另有隱情套利,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布鹤耍,位于F島的核電站肉迫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏稿黄。R本人自食惡果不足惜喊衫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杆怕。 院中可真熱鬧族购,春花似錦、人聲如沸陵珍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)互纯。三九已至瑟幕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背只盹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工辣往, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人殖卑。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓站削,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親懦鼠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钻哩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容