sds

簡(jiǎn)介

在redis內(nèi)部,string數(shù)據(jù)結(jié)構(gòu)的value主要以int、sds作為結(jié)構(gòu)存儲(chǔ)。int用來(lái)存放整型,sds用來(lái)存放字節(jié)、字符串、浮點(diǎn)型數(shù)據(jù)。

sds結(jié)構(gòu)分析

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
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種類(lèi)型的header(sdshdr5 不再被使用了)。使得不同長(zhǎng)度的字符串可以使用不同大小的header,從而節(jié)省內(nèi)存。

接下來(lái)我們定義一個(gè)通用的sds結(jié)構(gòu),如下:

struct  sdshdr {
    XXX len; /* used */
    XXX alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
  • len:表示字符串的真正長(zhǎng)度(不包含NULL結(jié)束符);
  • alloc:表示字符串的最大容量(不包含頭部和NULL結(jié)束符);
  • flags:占用一個(gè)字節(jié),其中的最低3個(gè)bit用來(lái)表示header的類(lèi)型,其余5個(gè)bit未使用。對(duì)應(yīng)類(lèi)型如下:
#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
  • buf:字符數(shù)組,存儲(chǔ)實(shí)際內(nèi)容。SDS 遵循 C 字符串以空字符結(jié)尾的慣例, 保存NULL字符的 1 字節(jié)空間不計(jì)算在SDS的len屬性、alloc屬性里面, 并且為空字符分配額外的 1 字節(jié)空間。

sds優(yōu)勢(shì)

C 語(yǔ)言使用長(zhǎng)度為 N+1 的字符數(shù)組來(lái)表示長(zhǎng)度為 N 的字符串, 并且字符數(shù)組的最后一個(gè)元素總是NULL字符 '\0' 。這種簡(jiǎn)單的字符串表示方式, 并不能滿(mǎn)足 Redis 對(duì)字符串在安全性、效率、以及功能方面的要求。

常數(shù)復(fù)雜度獲取字符串長(zhǎng)度

C 字符串并不記錄自身的長(zhǎng)度信息, 所以為了獲取一個(gè) C 字符串的長(zhǎng)度, 程序必須遍歷整個(gè)字符串, 對(duì)遇到的每個(gè)字符進(jìn)行計(jì)數(shù), 直到遇到代表字符串結(jié)尾的空字符為止, 這個(gè)操作的復(fù)雜度為 O(N);
sds記錄字符串長(zhǎng)度,可以直接返回len,復(fù)雜度為 O(1);

杜絕緩沖區(qū)溢出

C 字符串不記錄自身長(zhǎng)度帶來(lái)的另一個(gè)問(wèn)題是容易造成緩沖區(qū)溢出。


image.png

如上圖所示,str1="hello",str2="world",str1與str2存儲(chǔ)在連續(xù)的內(nèi)存空間,接著對(duì)str1拼接“&hello”(c語(yǔ)言提供<string.h>/strcat 函數(shù)可以將 src 字符串中的內(nèi)容拼接到 dest 字符串的末尾:char *strcat(char *dest, const char *src);),可以看到原先str2="world"被修改了。

sds的空間分配策略完全杜絕了發(fā)生緩沖區(qū)溢出的可能性: 當(dāng)sds API 需要對(duì)sds進(jìn)行修改時(shí), API 會(huì)先檢查sds的空間是否滿(mǎn)足修改所需的要求, 如果不滿(mǎn)足的話(huà), API 會(huì)自動(dòng)將sds的空間擴(kuò)展至執(zhí)行修改所需的大小, 然后才執(zhí)行實(shí)際的修改操作, 所以使用sds既不需要手動(dòng)修改sds的空間大小, 也不會(huì)出現(xiàn)前面所說(shuō)的緩沖區(qū)溢出問(wèn)題。

減少修改字符串時(shí)帶來(lái)的內(nèi)存重分配次數(shù)

每次增長(zhǎng)或者縮短一個(gè) C 字符串, 程序都總要對(duì)保存這個(gè) C 字符串的數(shù)組進(jìn)行一次內(nèi)存重分配操作。內(nèi)存重分配涉及復(fù)雜的算法, 并且可能需要執(zhí)行系統(tǒng)調(diào)用, 所以它通常是一個(gè)比較耗時(shí)的操作。

在sds中, buf 數(shù)組的長(zhǎng)度不一定就是字符數(shù)量加一, 數(shù)組里面可以包含未使用的字節(jié), 而這些字節(jié)的數(shù)量就由 SDS 的 alloc屬性 - len屬性來(lái)決定。
sds通過(guò)空間預(yù)分配和惰性空間釋放兩種優(yōu)化策略來(lái)減少修改字符串時(shí)帶來(lái)的內(nèi)存重分配次數(shù)。

空間預(yù)分配

空間預(yù)分配策略如下:

  • 如果對(duì)sds進(jìn)行修改之后, len < 1 MB , 則alloc = 2 * len,buf長(zhǎng)度 = 2*len + 1;
  • 如果對(duì)sds進(jìn)行修改之后, len >= 1 MB , 則alloc = len + 1MB,buf長(zhǎng)度 = len + 1MB + 1byte;

通過(guò)空間預(yù)分配策略, Redis 可以減少連續(xù)執(zhí)行字符串增長(zhǎng)操作所需的內(nèi)存重分配次數(shù)。

惰性空間釋放

惰性空間釋放用于優(yōu)化sds的字符串縮短操作: 當(dāng)需要縮短SDS保存的字符串時(shí),程序并不立即使用內(nèi)存重分配來(lái)回收縮短后多出來(lái)的字節(jié), 而是先預(yù)留著,并等待將來(lái)使用。

當(dāng)然, sds也提供了相應(yīng)的API,讓我們可以在有需要時(shí),真正地釋放 sds里面的未使用空間, 所以不用擔(dān)心惰性空間釋放策略會(huì)造成內(nèi)存浪費(fèi)。

二進(jìn)制安全

對(duì)于C語(yǔ)言來(lái)說(shuō),C 字符串中的字符必須符合某種編碼(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含NULL字符, 否則最先被程序讀入的空字符將被誤認(rèn)為是字符串結(jié)尾 —— 這些限制使得 C 字符串只能保存文本數(shù)據(jù), 而不能保存像圖片、音頻、視頻、壓縮文件這樣的二進(jìn)制數(shù)據(jù)。

sds的 API 都是二進(jìn)制安全的(binary-safe): 所有sds API 都會(huì)以處理二進(jìn)制的方式來(lái)處理sds存放在buf數(shù)組里的數(shù)據(jù), 程序不會(huì)對(duì)其中的數(shù)據(jù)做任何額外的處理,數(shù)據(jù)在寫(xiě)入時(shí)是什么樣的,它被讀取時(shí)就是什么樣。也就是說(shuō)相比C語(yǔ)言字符串,SDS會(huì)通過(guò)len來(lái)限制讀取長(zhǎng)度,而非“\0”,所以保證了二進(jìn)制安全。

因此sds既可以保存文本數(shù)據(jù), 又能保存二進(jìn)制數(shù)據(jù)。

兼容部分 C 字符串函數(shù)

通過(guò)遵循C字符串以NULL字符結(jié)尾的慣例, sds可以使用部分 <string.h> 庫(kù)中的函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,285評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,548評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,328評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,878評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,098評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,554評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,725評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,971評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,361評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,613評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,339評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,695評(píng)論 2 370

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