探索Redis設(shè)計與實現(xiàn)3:Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)詳解——sds

本文轉(zhuǎn)自互聯(lián)網(wǎng)

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫宴杀,更多精彩內(nèi)容請到我的倉庫里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發(fā)于我的個人博客:

www.how2playlife.com

本文是微信公眾號【Java技術(shù)江湖】的《探索Redis設(shè)計與實現(xiàn)》其中一篇,本文部分內(nèi)容來源于網(wǎng)絡(luò)拾因,為了把本文主題講得清晰透徹旺罢,也整合了很多我認(rèn)為不錯的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章绢记,如有侵權(quán)扁达,請聯(lián)系作者。

該系列博文會告訴你如何從入門到進(jìn)階庭惜,Redis基本的使用方法罩驻,Redis的基本數(shù)據(jù)結(jié)構(gòu)穗酥,以及一些進(jìn)階的使用方法护赊,同時也需要進(jìn)一步了解Redis的底層數(shù)據(jù)結(jié)構(gòu)惠遏,再接著,還會帶來Redis主從復(fù)制骏啰、集群节吮、分布式鎖等方面的相關(guān)內(nèi)容,以及作為緩存的一些使用方法和注意事項判耕,以便讓你更完整地了解整個Redis相關(guān)的技術(shù)體系透绩,形成自己的知識框架。

如果對本系列文章有什么建議壁熄,或者是有什么疑問的話帚豪,也可以關(guān)注公眾號【Java技術(shù)江湖】聯(lián)系作者,歡迎你參與本系列博文的創(chuàng)作和修訂草丧。

前言

本文是《Redis內(nèi)部數(shù)據(jù)結(jié)構(gòu)詳解》系列的第二篇狸臣,講述Redis中使用最多的一個基礎(chǔ)數(shù)據(jù)結(jié)構(gòu):sds。

不管在哪門編程語言當(dāng)中昌执,字符串都幾乎是使用最多的數(shù)據(jù)結(jié)構(gòu)烛亦。sds正是在Redis中被廣泛使用的字符串結(jié)構(gòu),它的全稱是Simple Dynamic String懂拾。與其它語言環(huán)境中出現(xiàn)的字符串相比煤禽,它具有如下顯著的特點:

可動態(tài)擴展內(nèi)存。sds表示的字符串其內(nèi)容可以修改岖赋,也可以追加檬果。在很多語言中字符串會分為mutable和immutable兩種,顯然sds屬于mutable類型的唐断。
二進(jìn)制安全(Binary Safe)汁汗。sds能存儲任意二進(jìn)制數(shù)據(jù),而不僅僅是可打印字符栗涂。
與傳統(tǒng)的C語言字符串類型兼容知牌。這個的含義接下來馬上會討論。
看到這里斤程,很多對Redis有所了解的同學(xué)可能已經(jīng)產(chǎn)生了一個疑問:Redis已經(jīng)對外暴露了一個字符串結(jié)構(gòu)角寸,叫做string,那這里所說的sds到底和string是什么關(guān)系呢忿墅?可能有人會猜:string是基于sds實現(xiàn)的扁藕。這個猜想已經(jīng)非常接近事實,但在描述上還不太準(zhǔn)確疚脐。有關(guān)string和sds之間關(guān)系的詳細(xì)分析亿柑,我們放在后面再講。現(xiàn)在為了方便討論棍弄,讓我們先暫時簡單地認(rèn)為望薄,string的底層實現(xiàn)就是sds疟游。

在討論sds的具體實現(xiàn)之前,我們先站在Redis使用者的角度颁虐,來觀察一下string所支持的一些主要操作。下面是一個操作示例:

image

Redis string操作示例

以上這些操作都比較簡單另绩,我們簡單解釋一下:

初始的字符串的值設(shè)為”tielei”花嘶。
第3步通過append命令對字符串進(jìn)行了追加,變成了”tielei zhang”椭员。
然后通過setbit命令將第53個bit設(shè)置成了1。bit的偏移量從左邊開始算拆撼,從0開始。其中第48~55bit是中間的空格那個字符闸度,它的ASCII碼是0x20。將第53個bit設(shè)置成1之后留量,它的ASCII碼變成了0x24,打印出來就是’’楼熄。因此浩峡,現(xiàn)在字符串的值變成了”tieleizhang”可岂。
最后通過getrange取從倒數(shù)第5個字節(jié)到倒數(shù)第1個字節(jié)的內(nèi)容,得到”zhang”翰灾。
這些命令的實現(xiàn)缕粹,有一部分是和sds的實現(xiàn)有關(guān)的。下面我們開始詳細(xì)討論纸淮。

sds的數(shù)據(jù)結(jié)構(gòu)定義

我們知道平斩,在C語言中,字符串是以’\0’字符結(jié)尾(NULL結(jié)束符)的字符數(shù)組來存儲的咽块,通常表達(dá)為字符指針的形式(char *)绘面。它不允許字節(jié)0出現(xiàn)在字符串中間,因此,它不能用來存儲任意的二進(jìn)制數(shù)據(jù)揭璃。

我們可以在sds.h中找到sds的類型定義:

typedef char *sds;
肯定有人感到困惑了晚凿,竟然sds就等同于char *?我們前面提到過塘辅,sds和傳統(tǒng)的C語言字符串保持類型兼容,因此它們的類型定義是一樣的皆撩,都是char *扣墩。在有些情況下,需要傳入一個C語言字符串的地方扛吞,也確實可以傳入一個sds呻惕。但是,sds和char *并不等同滥比。sds是Binary Safe的亚脆,它可以存儲任意二進(jìn)制數(shù)據(jù),不能像C語言字符串那樣以字符’\0’來標(biāo)識字符串的結(jié)束盲泛,因此它必然有個長度字段濒持。但這個長度字段在哪里呢?實際上sds還包含一個header結(jié)構(gòu):

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[];
};

sds一共有5種類型的header寺滚。之所以有5種柑营,是為了能讓不同長度的字符串可以使用不同大小的header。這樣村视,短字符串就能使用較小的header,從而節(jié)省內(nèi)存奶赔。

一個sds字符串的完整結(jié)構(gòu)站刑,由在內(nèi)存地址上前后相鄰的兩部分組成:

一個header笛钝。通常包含字符串的長度(len)玻靡、最大容量(alloc)和flags囤捻。sdshdr5有所不同邻寿。
一個字符數(shù)組。這個字符數(shù)組的長度等于最大容量+1挡毅。真正有效的字符串?dāng)?shù)據(jù)跪呈,其長度通常小于最大容量耗绿。在真正的字符串?dāng)?shù)據(jù)之后误阻,是空余未用的字節(jié)(一般以字節(jié)0填充)究反,允許在不重新分配內(nèi)存的前提下讓字符串?dāng)?shù)據(jù)向后做有限的擴展奴紧。在真正的字符串?dāng)?shù)據(jù)之后黍氮,還有一個NULL結(jié)束符沫浆,即ASCII碼為0的’\0’字符专执。這是為了和傳統(tǒng)C字符串兼容本股。之所以字符數(shù)組的長度比最大容量多1個字節(jié),就是為了在字符串長度達(dá)到最大容量時仍然有1個字節(jié)存放NULL結(jié)束符。
除了sdshdr5之外,其它4個header的結(jié)構(gòu)都包含3個字段:

len: 表示字符串的真正長度(不包含NULL結(jié)束符在內(nèi))承边。
alloc: 表示字符串的最大容量(不包含最后多余的那個字節(jié))博助。
flags: 總是占用一個字節(jié)富岳。其中的最低3個bit用來表示header的類型城瞎。header的類型共有5種脖镀,在sds.h中有常量定義蜒灰。
#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

sds的數(shù)據(jù)結(jié)構(gòu)强窖,我們有必要非常仔細(xì)地去解析它翅溺。

Redis dict結(jié)構(gòu)舉例

上圖是sds的一個內(nèi)部結(jié)構(gòu)的例子咙崎。圖中展示了兩個sds字符串s1和s2的內(nèi)存結(jié)構(gòu)褪猛,一個使用sdshdr8類型的header伊滋,另一個使用sdshdr16類型的header笑旺。但它們都表達(dá)了同樣的一個長度為6的字符串的值:”tielei”燥撞。下面我們結(jié)合代碼物舒,來解釋每一部分的組成冠胯。

sds的字符指針(s1和s2)就是指向真正的數(shù)據(jù)(字符數(shù)組)開始的位置荠察,而header位于內(nèi)存地址較低的方向悉盆。在sds.h中有一些跟解析header有關(guān)的宏定義:

#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

其中SDS_HDR用來從sds字符串獲得header起始位置的指針焕盟,比如SDS_HDR(8, s1)表示s1的header指針脚翘,SDS_HDR(16, s2)表示s2的header指針来农。

當(dāng)然沃于,使用SDS_HDR之前我們必須先知道到底是哪一種header繁莹,這樣我們才知道SDS_HDR第1個參數(shù)應(yīng)該傳什么蒋困。由sds字符指針獲得header類型的方法是雪标,先向低地址方向偏移1個字節(jié)的位置村刨,得到flags字段嵌牺。比如龄糊,s1[-1]和s2[-1]分別獲得了s1和s2的flags的值炫惩。然后取flags的最低3個bit得到header的類型他嚷。

由于s1[-1] == 0x01 == SDS_TYPE_8筋蓖,因此s1的header類型是sdshdr8粘咖。
由于s2[-1] == 0x02 == SDS_TYPE_16瓮下,因此s2的header類型是sdshdr16唱捣。
有了header指針,就能很快定位到它的len和alloc字段:

s1的header中战虏,len的值為0x06烦感,表示字符串?dāng)?shù)據(jù)長度為6手趣;alloc的值為0x80绿渣,表示字符數(shù)組最大容量為128中符。
s2的header中淀散,len的值為0x0006,表示字符串?dāng)?shù)據(jù)長度為6慢蜓;alloc的值為0x03E8胀瞪,表示字符數(shù)組最大容量為1000凄诞。(注意:圖中是按小端地址構(gòu)成)
在各個header的類型定義中帆谍,還有幾個需要我們注意的地方:

在各個header的定義中使用了attribute ((packed))汛蝙,是為了讓編譯器以緊湊模式來分配內(nèi)存朴肺。如果沒有這個屬性戈稿,編譯器可能會為struct的字段做優(yōu)化對齊鞍盗,在其中填充空字節(jié)。那樣的話肋乍,就不能保證header和sds的數(shù)據(jù)部分緊緊前后相鄰墓造,也不能按照固定向低地址方向偏移1個字節(jié)的方式來獲取flags字段了觅闽。

在各個header的定義中最后有一個char buf[]谱煤。我們注意到這是一個沒有指明長度的字符數(shù)組刘离,這是C語言中定義字符數(shù)組的一種特殊寫法,稱為柔性數(shù)組(flexible array member)茧痕,只能定義在一個結(jié)構(gòu)體的最后一個字段上踪旷。

它在這里只是起到一個標(biāo)記的作用令野,表示在flags字段后面就是一個字符數(shù)組徽级,或者說餐抢,它指明了緊跟在flags字段后面的這個字符數(shù)組在結(jié)構(gòu)體中的偏移位置碳锈。而程序在為header分配的內(nèi)存的時候欺抗,它并不占用內(nèi)存空間佩迟。

如果計算sizeof(struct sdshdr16)的值报强,那么結(jié)果是5個字節(jié)拱燃,其中沒有buf字段召嘶。
sdshdr5與其它幾個header結(jié)構(gòu)不同弄跌,它不包含alloc字段尝苇,而長度使用flags的高5位來存儲。

因此直撤,它不能為字符串分配空余空間蜕着。如果字符串需要動態(tài)增長承匣,那么它就必然要重新分配內(nèi)存才行韧骗。所以說,這種類型的sds字符串更適合存儲靜態(tài)的短字符串(長度小于32)众眨。

至此娩梨,我們非常清楚地看到了:sds字符串的header狈定,其實隱藏在真正的字符串?dāng)?shù)據(jù)的前面(低地址方向)纽什。這樣的一個定義芦缰,有如下幾個好處:

header和數(shù)據(jù)相鄰让蕾,而不用分成兩塊內(nèi)存空間來單獨分配探孝。這有利于減少內(nèi)存碎片顿颅,提高存儲效率(memory efficiency)粱腻。

雖然header有多個類型栖疑,但sds可以用統(tǒng)一的char *來表達(dá)卿闹。且它與傳統(tǒng)的C語言字符串保持類型兼容锻霎。

如果一個sds里面存儲的是可打印字符串旋恼,那么我們可以直接把它傳給C函數(shù)冰更,比如使用strcmp比較字符串大小蜀细,或者使用printf進(jìn)行打印奠衔。
弄清了sds的數(shù)據(jù)結(jié)構(gòu)塘娶,它的具體操作函數(shù)就比較好理解了刁岸。

sds的一些基礎(chǔ)函數(shù)
sdslen(const sds s): 獲取sds字符串長度膝宁。
sdssetlen(sds s, size_t newlen): 設(shè)置sds字符串長度。
sdsinclen(sds s, size_t inc): 增加sds字符串長度击敌。
sdsalloc(const sds s): 獲取sds字符串容量沃斤。
sdssetalloc(sds s, size_t newlen): 設(shè)置sds字符串容量徘公。
sdsavail(const sds s): 獲取sds字符串空余空間(即alloc - len)关面。
sdsHdrSize(char type): 根據(jù)header類型得到header大小等太。
sdsReqType(size_t string_size):

根據(jù)字符串?dāng)?shù)據(jù)長度計算所需要的header類型。
這里我們挑選sdslen和sdsReqType的代碼瞻想,察看一下内边。

static inline size_t sdslen(const sds s) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)
        return SDS_TYPE_5;
    if (string_size < 1<<8)
        return SDS_TYPE_8;
    if (string_size < 1<<16)
        return SDS_TYPE_16;
    if (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
}

跟前面的分析類似竿音,sdslen先用s[-1]向低地址方向偏移1個字節(jié)和屎,得到flags;然后與SDS_TYPE_MASK進(jìn)行按位與春瞬,得到header類型柴信;然后根據(jù)不同的header類型,調(diào)用SDS_HDR得到header起始指針宽气,進(jìn)而獲得len字段随常。

通過sdsReqType的代碼,很容易看到:

長度在0和2^5-1之間萄涯,選用SDS_TYPE_5類型的header绪氛。
長度在25和28-1之間,選用SDS_TYPE_8類型的header涝影。
長度在28和216-1之間,選用SDS_TYPE_16類型的header握童。
長度在216和232-1之間英古,選用SDS_TYPE_32類型的header蛮浑。
長度大于232的蕴掏,選用SDS_TYPE_64類型的header即供。能表示的最大長度為264-1。
注:sdsReqType的實現(xiàn)代碼,直到3.2.0,它在長度邊界值上都一直存在問題车份,直到最近3.2 branch上的commit 6032340才修復(fù)。

sds的創(chuàng)建和銷毀

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */
 
    sh = s_malloc(hdrlen+initlen+1);
    if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}
 
sds sdsempty(void) {
    return sdsnewlen("",0);
}
 
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}
 
void sdsfree(sds s) {
    if (s == NULL) return;
    s_free((char*)s-sdsHdrSize(s[-1]));
}

sdsnewlen創(chuàng)建一個長度為initlen的sds字符串轰坊,并使用init指向的字符數(shù)組(任意二進(jìn)制數(shù)據(jù))來初始化數(shù)據(jù)。如果init為NULL,那么使用全0來初始化數(shù)據(jù)沉衣。它的實現(xiàn)中肥隆,我們需要注意的是:

如果要創(chuàng)建一個長度為0的空字符串,那么不使用SDS_TYPE_5類型的header,而是轉(zhuǎn)而使用SDS_TYPE_8類型的header孙技。這是因為創(chuàng)建的空字符串一般接下來的操作很可能是追加數(shù)據(jù)哈雏,但SDS_TYPE_5類型的sds字符串不適合追加數(shù)據(jù)(會引發(fā)內(nèi)存重新分配)。
需要的內(nèi)存空間一次性進(jìn)行分配毅舆,其中包含三部分:header、數(shù)據(jù)泳叠、最后的多余字節(jié)(hdrlen+initlen+1)瞒大。
初始化的sds字符串?dāng)?shù)據(jù)最后會追加一個NULL結(jié)束符(s[initlen] = ‘\0’)。
關(guān)于sdsfree德绿,需要注意的是:內(nèi)存要整體釋放都许,所以要先計算出header起始指針钱雷,把它傳給s_free函數(shù)。這個指針也正是在sdsnewlen中調(diào)用s_malloc返回的那個地址信夫。

sds的連接(追加)操作

sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);
 
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    memcpy(s+curlen, t, len);
    sdssetlen(s, curlen+len);
    s[curlen+len] = '\0';
    return s;
}
 
sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}
 
sds sdscatsds(sds s, const sds t) {
    return sdscatlen(s, t, sdslen(t));
}
 
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
 
    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;
 
    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
 
    type = sdsReqType(newlen);
 
    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
 
    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, newlen);
    return s;
}

sdscatlen將t指向的長度為len的任意二進(jìn)制數(shù)據(jù)追加到sds字符串s的后面。本文開頭演示的string的append命令购裙,內(nèi)部就是調(diào)用sdscatlen來實現(xiàn)的夯到。

在sdscatlen的實現(xiàn)中晃听,先調(diào)用sdsMakeRoomFor來保證字符串s有足夠的空間來追加長度為len的數(shù)據(jù)越平。sdsMakeRoomFor可能會分配新的內(nèi)存,也可能不會樊销。

sdsMakeRoomFor是sds實現(xiàn)中很重要的一個函數(shù)腺占。關(guān)于它的實現(xiàn)代碼论矾,我們需要注意的是:

如果原來字符串中的空余空間夠用(avail >= addlen),那么它什么也不做淑翼,直接返回惠豺。

如果需要分配空間饮寞,它會比實際請求的要多分配一些寞钥,以防備接下來繼續(xù)追加咨油。它在字符串已經(jīng)比較長的情況下要至少多分配SDS_MAX_PREALLOC個字節(jié),這個常量在sds.h中定義為(1024*1024)=1MB。

按分配后的空間大小蒜埋,可能需要更換header類型(原來header的alloc字段太短适瓦,表達(dá)不了增加后的容量)竿开。

如果需要更換header谱仪,那么整個字符串空間(包括header)都需要重新分配(s_malloc),并拷貝原來的數(shù)據(jù)到新的位置否彩。

如果不需要更換header(原來的header夠用)疯攒,那么調(diào)用一個比較特殊的s_realloc,試圖在原來的地址上重新分配空間列荔。s_realloc的具體實現(xiàn)得看Redis編譯的時候選用了哪個allocator(在Linux上默認(rèn)使用jemalloc)敬尺。

但不管是哪個realloc的實現(xiàn),它所表達(dá)的含義基本是相同的:它盡量在原來分配好的地址位置重新分配肌毅,如果原來的地址位置有足夠的空余空間完成重新分配筷转,那么它返回的新地址與傳入的舊地址相同;否則悬而,它分配新的地址塊呜舒,并進(jìn)行數(shù)據(jù)搬遷。參見http://man.cx/realloc笨奠。

從sdscatlen的函數(shù)接口袭蝗,我們可以看到一種使用模式:調(diào)用它的時候,傳入一個舊的sds變量般婆,然后它返回一個新的sds變量到腥。由于它的內(nèi)部實現(xiàn)可能會造成地址變化,因此調(diào)用者在調(diào)用完之后蔚袍,原來舊的變量就失效了乡范,而都應(yīng)該用新返回的變量來替換。不僅僅是sdscatlen函數(shù)啤咽,sds中的其它函數(shù)(比如sdscpy晋辆、sdstrim、sdsjoin等)宇整,還有Redis中其它一些能自動擴展內(nèi)存的數(shù)據(jù)結(jié)構(gòu)(如ziplist)瓶佳,也都是同樣的使用模式。

淺談sds與string的關(guān)系

現(xiàn)在我們回過頭來看看本文開頭給出的string操作的例子鳞青。

append操作使用sds的sdscatlen來實現(xiàn)霸饲。前面已經(jīng)提到。

setbit和getrange都是先根據(jù)key取到整個sds字符串臂拓,然后再從字符串選取或修改指定的部分厚脉。由于sds就是一個字符數(shù)組,所以對它的某一部分進(jìn)行操作似乎都比較簡單埃儿。

但是器仗,string除了支持這些操作之外,當(dāng)它存儲的值是個數(shù)字的時候,它還支持incr精钮、decr等操作威鹿。那么,當(dāng)string存儲數(shù)字值的時候轨香,它的內(nèi)部存儲還是sds嗎忽你?

實際上,不是了臂容。而且科雳,這種情況下,setbit和getrange的實現(xiàn)也會有所不同脓杉。這些細(xì)節(jié)糟秘,我們放在下一篇介紹robj的時候再進(jìn)行系統(tǒng)地討論。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末球散,一起剝皮案震驚了整個濱河市尿赚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蕉堰,老刑警劉巖凌净,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屋讶,居然都是意外死亡冰寻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門皿渗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斩芭,“玉大人,你說我怎么就攤上這事乐疆∶胄” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵诀拭,是天一觀的道長。 經(jīng)常有香客問我煤蚌,道長耕挨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任尉桩,我火速辦了婚禮筒占,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜘犁。我一直安慰自己翰苫,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奏窑,像睡著了一般导披。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上埃唯,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天撩匕,我揣著相機與錄音,去河邊找鬼墨叛。 笑死止毕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漠趁。 我是一名探鬼主播扁凛,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼闯传!你這毒婦竟也來了谨朝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤丸边,失蹤者是張志新(化名)和其女友劉穎叠必,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妹窖,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡纬朝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骄呼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片共苛。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜓萄,靈堂內(nèi)的尸體忽然破棺而出隅茎,到底是詐尸還是另有隱情,我是刑警寧澤嫉沽,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布辟犀,位于F島的核電站,受9級特大地震影響绸硕,放射性物質(zhì)發(fā)生泄漏堂竟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一玻佩、第九天 我趴在偏房一處隱蔽的房頂上張望出嘹。 院中可真熱鬧,春花似錦咬崔、人聲如沸税稼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽郎仆。三九已至只祠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丸升,已是汗流浹背铆农。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狡耻,地道東北人墩剖。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像夷狰,于是被迫代替她去往敵國和親岭皂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

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