簡(jiǎn)介redis之SDS

前言

在官方對(duì)redis的介紹中我們可以看到醒目的一句話:
Redis is not a plain key-value store,it is actually a data structures server
redis訪問速度之所以那么快其一要?dú)w功于他是內(nèi)存型數(shù)據(jù)庫(kù)祝高。其二就要?dú)w功于它對(duì)數(shù)據(jù)存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)济欢,即上面這句所強(qiáng)調(diào)的他更加是數(shù)據(jù)結(jié)構(gòu)服務(wù)器。
關(guān)于redis數(shù)據(jù)結(jié)構(gòu)從使用者角度出發(fā)有:
1.string
2.list
3.hash
4.set
5.sorted set
這也是redis服務(wù)器提供的外部接口
從底層實(shí)現(xiàn)角度出發(fā)有:
1.sds
2.dict
3.skiptlist
4.quicklist
5.ziplist
其中 string類型只由單一的sds實(shí)現(xiàn)

SDS(Simple Dynamic String)

源碼位子:src/sds.c,src/sds.h
在sds.h中 提供了sdshdr5/8/16/32/64這幾種的sds的實(shí)現(xiàn)

/* 以SDS8為例*/
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 已使用空間大小 */
    uint8_t alloc; /* 總共可用的字符空間大小甸各,應(yīng)該是實(shí)際buf的大小減1(因?yàn)閏字符串末尾必須是\0,不計(jì)算在內(nèi)) */
    unsigned char flags; /* 標(biāo)志位魏保,主要是識(shí)別這是sdshdr幾,目前只用了3位懈词,還有5位空余 */
    char buf[];   /* 真正存儲(chǔ)字符串的地方 */
};

其余大致相同通過(guò)flag來(lái)判斷是sds幾

為什么稱其為簡(jiǎn)單動(dòng)態(tài)字符串慎菲?

1.與C字符串的區(qū)別
C語(yǔ)言采用N+1的字符數(shù)組來(lái)表示字符串,且末尾置'\0'
相較于c原生的字符串课幕,sds多了len厦坛、alloc、flag三個(gè)字段來(lái)存儲(chǔ)一些額外的信息乍惊,redis考慮到了字符串拼接時(shí)帶來(lái)的巨大損耗粪般,所以每次新建sds的時(shí)候會(huì)預(yù)分配一些空間來(lái)應(yīng)對(duì)未來(lái)的增長(zhǎng)
因此C獲取字符串長(zhǎng)度的時(shí)間復(fù)雜度為O(n),須全部遍歷污桦,SDS只需讀取計(jì)算len字段即可亩歹,且因?yàn)轭A(yù)分配了額外的空間杜絕了緩存溢出和減少了修改字符串時(shí)的內(nèi)存分配次數(shù)匙监,且sds是以len判斷字符串結(jié)尾中間是否出現(xiàn)'\0'與其無(wú)關(guān),是二進(jìn)制安全的

為啥要設(shè)計(jì)多種sds
閱讀sds.c中的sdsnewlen方法(sds初始化從sdsnew進(jìn)入到sdsnewlen)

// sds在初始化時(shí)需要傳入長(zhǎng)度initlen
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    //根據(jù)初始化長(zhǎng)度確定使用哪種sds
    char type = sdsReqType(initlen);
    //空字符串處理默認(rèn)類型sds8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* sds->flag*/
    // redis 自己hock內(nèi)存分配
    sh = s_malloc(hdrlen+initlen+1);
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
//注意這里返回的sh并不是直接指向sds的指針小作,而是指向sds中字符串的指針
// sds指針需要根據(jù)sh和hdrlen計(jì)算
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
//根據(jù)type類型分配內(nèi)存
    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);
//為方便使用C內(nèi)置字符串函數(shù)亭姥,末尾置'\0'
    s[initlen] = '\0';
    return s;
}

閱讀switch分支我們可以看到根據(jù)初始化長(zhǎng)度,小于3的使用sds5(這個(gè)基本不用)顾稀,小于2^8的長(zhǎng)度使用sds8达罗,以此類推,這樣子sds8的len和alloc只占用兩個(gè)字節(jié)静秆,比較短字符串可能非常多粮揉,所以節(jié)省下來(lái)的內(nèi)存還是非常可觀的(基本上是扣額外分配的內(nèi)存)
SDS空間不足要擴(kuò)容怎么辦
常見如字符串拼接抚笔,sds可能空間不足扶认。redis采用指數(shù)級(jí)擴(kuò)容方法

// 擴(kuò)大sds的實(shí)際可用空間,以便后續(xù)能拼接更多字符串殊橙。 
// 注意:這里實(shí)際不會(huì)改變sds的長(zhǎng)度辐宾,只是增加了更多可用的空間(buf) 
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; // SDS_TYPE_MASK = 7 
    int hdrlen;

    /* 如果有足夠的剩余空間,直接返回 */
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    // 在未超出SDS_MAX_PREALLOC前膨蛮,擴(kuò)容都是按2倍的方式擴(kuò)容叠纹,超出后只能遞增 
    if (newlen < SDS_MAX_PREALLOC)  // SDS_MAX_PREALLOC = 1024*1024
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);

    /*  在真正使用過(guò)程中不會(huì)用到type5,如果遇到type5直接使用type8*/
    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 {
        // 擴(kuò)容其實(shí)就是申請(qǐng)新的空間敞葛,然后把舊數(shù)據(jù)挪過(guò)去  
        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;
}

對(duì)于SDS_MAX_PREALLOC的宏定義為

#define SDS_MAX_PREALLOC (1024*1024)

在SDS_MAX_PREALLOC范圍內(nèi)以指數(shù)2倍對(duì)buf擴(kuò)容誉察,超出則每次加SDS_MAX_PREALLOC

總結(jié)

sds(簡(jiǎn)單動(dòng)態(tài)字符串)特點(diǎn),預(yù)先分配內(nèi)存惹谐,記錄字符串長(zhǎng)度持偏,在原字符串?dāng)?shù)組里新增加一串字符串。

新長(zhǎng)度newlen為原len+addlen豺鼻,若newlen小于1M,則為SDS分配新的內(nèi)存大小為2*newlen款慨;若newlen大于等于1M儒飒,則SDS分配新的內(nèi)存大小為newlen + 1M

SDS是以len字段來(lái)判斷是否到達(dá)字符串末尾,而不是以'\0'判斷結(jié)尾檩奠。所以sds存儲(chǔ)的字符串中間可以出現(xiàn)'\0'桩了,即sds字符串是二進(jìn)制安全的。

當(dāng)要清空一個(gè)SDS時(shí)埠戳,并不真正釋放其內(nèi)存井誉,而是設(shè)置len字段為0即可,這樣當(dāng)之后再次使用到該SDS時(shí)整胃,可避免重新分配內(nèi)存颗圣,從而提高效率。

SDS的好處就是通過(guò)預(yù)分配內(nèi)存和維護(hù)字符串長(zhǎng)度,實(shí)現(xiàn)動(dòng)態(tài)字符串在岂。

試試回答以下問題

1.為啥redis要自己封裝一個(gè)string類型
2.什么是動(dòng)態(tài)簡(jiǎn)單
3.如何兼容C字符串

參考

1.redis源碼刨析之SDS
2.如何閱讀redis源碼
3.升入理解redis之簡(jiǎn)單動(dòng)態(tài)字符串

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奔则,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蔽午,更是在濱河造成了極大的恐慌易茬,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,423評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件及老,死亡現(xiàn)場(chǎng)離奇詭異抽莱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)骄恶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門食铐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人叠蝇,你說(shuō)我怎么就攤上這事璃岳。” “怎么了悔捶?”我有些...
    開封第一講書人閱讀 157,019評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵铃慷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蜕该,道長(zhǎng)犁柜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,443評(píng)論 1 283
  • 正文 為了忘掉前任堂淡,我火速辦了婚禮馋缅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绢淀。我一直安慰自己萤悴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評(píng)論 6 385
  • 文/花漫 我一把揭開白布皆的。 她就那樣靜靜地躺著覆履,像睡著了一般。 火紅的嫁衣襯著肌膚如雪费薄。 梳的紋絲不亂的頭發(fā)上硝全,一...
    開封第一講書人閱讀 49,798評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音楞抡,去河邊找鬼伟众。 笑死,一個(gè)胖子當(dāng)著我的面吹牛召廷,可吹牛的內(nèi)容都是我干的凳厢。 我是一名探鬼主播账胧,決...
    沈念sama閱讀 38,941評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼数初!你這毒婦竟也來(lái)了找爱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泡孩,失蹤者是張志新(化名)和其女友劉穎车摄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仑鸥,經(jīng)...
    沈念sama閱讀 44,152評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吮播,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了眼俊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意狠。...
    茶點(diǎn)故事閱讀 38,629評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疮胖,靈堂內(nèi)的尸體忽然破棺而出环戈,到底是詐尸還是另有隱情,我是刑警寧澤澎灸,帶...
    沈念sama閱讀 34,295評(píng)論 4 329
  • 正文 年R本政府宣布院塞,位于F島的核電站,受9級(jí)特大地震影響性昭,放射性物質(zhì)發(fā)生泄漏拦止。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評(píng)論 3 313
  • 文/蒙蒙 一糜颠、第九天 我趴在偏房一處隱蔽的房頂上張望汹族。 院中可真熱鬧,春花似錦其兴、人聲如沸顶瞒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榴徐。三九已至,卻和暖如春法绵,著一層夾襖步出監(jiān)牢的瞬間箕速,已是汗流浹背酪碘。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工朋譬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兴垦。 一個(gè)月前我還...
    沈念sama閱讀 46,333評(píng)論 2 360
  • 正文 我出身青樓徙赢,卻偏偏與公主長(zhǎng)得像字柠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狡赐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評(píng)論 2 348

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

  • 1. 五種數(shù)據(jù)結(jié)構(gòu)及應(yīng)用場(chǎng)景 詳見鏈接1詳見鏈接2 總結(jié): 字符串:字符串鍵的使用場(chǎng)景:如分布式鎖窑业、計(jì)數(shù)器、分布式...
    將軍紅閱讀 298評(píng)論 0 1
  • 引言 本系列文章開始講解 Redis 相關(guān)源碼枕屉,文章不定時(shí)更新常柄,并且周期可能會(huì)很長(zhǎng),請(qǐng)大家諒解搀擂。作為系列文章的開篇...
    Tubetrue01閱讀 235評(píng)論 0 0
  • Redis是用C語(yǔ)言實(shí)現(xiàn)的西潘,但是并沒有使用 C 語(yǔ)言傳統(tǒng)的字符串表示(以空字符結(jié)尾的字符數(shù)組,以下簡(jiǎn)稱 C 字符串...
    QaoKi閱讀 314評(píng)論 0 0
  • 我們知道redis是用C語(yǔ)言開發(fā)的哨颂,源代碼開源(小伙伴們可以去網(wǎng)上下載下來(lái)進(jìn)行閱讀)今天我們主要看的是SDS(Si...
    十年磨一劍1111閱讀 1,348評(píng)論 0 1
  • 字符串是Redis中一個(gè)重要的組成部分喷市,Redis沒有直接使用C語(yǔ)言自帶的字符串,而是自身構(gòu)建了一個(gè)簡(jiǎn)單動(dòng)態(tài)字符串...
    喵帕斯0_0閱讀 429評(píng)論 0 1