sds

简介

在redis内部,string数据结构的value主要以int、sds作为结构存储。int用来存放整型,sds用来存放字节、字符串、浮点型数据。

sds结构分析

/* 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种类型的header(sdshdr5 不再被使用了)。使得不同长度的字符串可以使用不同大小的header,从而节省内存。

接下来我们定义一个通用的sds结构,如下:

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:表示字符串的真正长度(不包含NULL结束符);
  • alloc:表示字符串的最大容量(不包含头部和NULL结束符);
  • flags:占用一个字节,其中的最低3个bit用来表示header的类型,其余5个bit未使用。对应类型如下:
#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:字符数组,存储实际内容。SDS 遵循 C 字符串以空字符结尾的惯例, 保存NULL字符的 1 字节空间不计算在SDS的len属性、alloc属性里面, 并且为空字符分配额外的 1 字节空间。

sds优势

C 语言使用长度为 N+1 的字符数组来表示长度为 N 的字符串, 并且字符数组的最后一个元素总是NULL字符 '\0' 。这种简单的字符串表示方式, 并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求。

常数复杂度获取字符串长度

C 字符串并不记录自身的长度信息, 所以为了获取一个 C 字符串的长度, 程序必须遍历整个字符串, 对遇到的每个字符进行计数, 直到遇到代表字符串结尾的空字符为止, 这个操作的复杂度为 O(N);
sds记录字符串长度,可以直接返回len,复杂度为 O(1);

杜绝缓冲区溢出

C 字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出。


image.png

如上图所示,str1="hello",str2="world",str1与str2存储在连续的内存空间,接着对str1拼接“&hello”(c语言提供<string.h>/strcat 函数可以将 src 字符串中的内容拼接到 dest 字符串的末尾:char *strcat(char *dest, const char *src);),可以看到原先str2="world"被修改了。

sds的空间分配策略完全杜绝了发生缓冲区溢出的可能性: 当sds API 需要对sds进行修改时, API 会先检查sds的空间是否满足修改所需的要求, 如果不满足的话, API 会自动将sds的空间扩展至执行修改所需的大小, 然后才执行实际的修改操作, 所以使用sds既不需要手动修改sds的空间大小, 也不会出现前面所说的缓冲区溢出问题。

减少修改字符串时带来的内存重分配次数

每次增长或者缩短一个 C 字符串, 程序都总要对保存这个 C 字符串的数组进行一次内存重分配操作。内存重分配涉及复杂的算法, 并且可能需要执行系统调用, 所以它通常是一个比较耗时的操作。

在sds中, buf 数组的长度不一定就是字符数量加一, 数组里面可以包含未使用的字节, 而这些字节的数量就由 SDS 的 alloc属性 - len属性来决定。
sds通过空间预分配和惰性空间释放两种优化策略来减少修改字符串时带来的内存重分配次数。

空间预分配

空间预分配策略如下:

  • 如果对sds进行修改之后, len < 1 MB , 则alloc = 2 * len,buf长度 = 2*len + 1;
  • 如果对sds进行修改之后, len >= 1 MB , 则alloc = len + 1MB,buf长度 = len + 1MB + 1byte;

通过空间预分配策略, Redis 可以减少连续执行字符串增长操作所需的内存重分配次数。

惰性空间释放

惰性空间释放用于优化sds的字符串缩短操作: 当需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是先预留着,并等待将来使用。

当然, sds也提供了相应的API,让我们可以在有需要时,真正地释放 sds里面的未使用空间, 所以不用担心惰性空间释放策略会造成内存浪费。

二进制安全

对于C语言来说,C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含NULL字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

sds的 API 都是二进制安全的(binary-safe): 所有sds API 都会以处理二进制的方式来处理sds存放在buf数组里的数据, 程序不会对其中的数据做任何额外的处理,数据在写入时是什么样的,它被读取时就是什么样。也就是说相比C语言字符串,SDS会通过len来限制读取长度,而非“\0”,所以保证了二进制安全。

因此sds既可以保存文本数据, 又能保存二进制数据。

兼容部分 C 字符串函数

通过遵循C字符串以NULL字符结尾的惯例, sds可以使用部分 <string.h> 库中的函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容