「Redis设计与实现」字符串篇

字符串存储规则

redis没有默认使用c字符串,仅在字符串字面量和使用c字符串。如果字符串为变量时,则使用SDS字符串。

SDS定义

sds.h/sdshdr结构

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 字符串长度 */
    uint8_t alloc; /* 分配内存大小(除去‘\0’) */
    unsigned char flags; /* 标志位(低三位表示类型,其余五位未使用) */
    char buf[]; /* 字符数组 */
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint8_t len; /* 字符串长度 */
    uint8_t alloc; /* 分配内存大小(除去‘\0’) */
    unsigned char flags; /* 标志位(低三位表示类型,其余五位未使用) */
    char buf[]; /* 字符数组 */
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint8_t len; /* 字符串长度 */
    uint8_t alloc; /* 分配内存大小(除去‘\0’) */
    unsigned char flags; /* 标志位(低三位表示类型,其余五位未使用) */
    char buf[]; /* 字符数组 */
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint8_t len; /* 字符串长度 */
    uint8_t alloc; /* 分配内存大小(除去‘\0’) */
    unsigned char flags; /* 标志位(低三位表示类型,其余五位未使用) */
    char buf[]; /* 字符数组 */
};

如上代码块注释,个人认为,Redis并未完全抛弃C语言字符串,只不过是在C语言字符串的基础上,通过封装其他的属性,构造出一个更加高效的字符串的封装结构,在早些的版本中记录了其长度(实际使用了多少)、剩余空间、以及字符数组,最新的版本3.2.4中,已经对SDS做了一定的改动记录了长度、分配内存大小(除去‘\0’)、标志位(低三位表示类型,其余五位未使用)、以及字符数组。在3.2.4版本里,redis细化了sdshdr长度值,不同长度的字符串可以选择对应位的sdshdr。

SDS与C字符串区别

  • 获取字符串长度
  1. C字符串不记录自己的长度,当需要获取长度的时候,需要遍历整个字符数组,对遇到的所有的字符进行计数,直到遇到空字符为止,执行时间复杂度为O(N)。
  2. SDS字符串保存了自身的长度,当需要获取长度的时候,直接可以获取到,这样把时间复杂度降低到O(1)。设置和更新SDS长度是由SDS的API在执行时自动完成的,使用SDS无须手动修改len属性。
  • 杜绝缓冲区溢出
    C语言不记录字符串长度的另一个弊端就是容易造成缓冲区溢出。举个例子,拼接两个字符串:wy和xx
    缓冲区溢出.png

假设前者为s1后者为s2,那么这里在进行字符串拼接的时候,一旦没有为s1从新分配适合的空间的话,那么拼接后的结果会溢出到s2的空间中去。
SDS在进行字符串拼接的时候,会自行检查是不是内存空间是不是 满足要求,如果不满足的话,自动进行分配,而且在进行分配空间的时候,会实行预先分配的策略。

  • 减少修改字符串时带来的内存重分配次数
    C语言字符串在进行字符串的扩充和收缩的时候,都会面临着内存空间的重新分配问题。
  1. 字符串拼接会产生字符串的内存空间的扩充,在拼接的过程中,原来的字符串的大小很可能小于拼接后的字符串的大小,那么这样的话,就会导致一旦忘记申请分配空间,就会导致内存的溢出。
  2. 字符串在进行收缩的时候,内存空间会相应的收缩,而如果在进行字符串的切割的时候,没有对内存的空间进行一个重新分配,那么这部分多出来的空间就成为了内存泄露。
    Redis在内存空间分配的问题上进行了优化,主要分为两个过程。
  3. 内存预分配
    内存空间进行分配的时候,预先分配一块多余的空间给当前的字符串对象,使得,在下一次字符串比如拼接的时候,尽可能保证其内存空间的足够用,不需要再去分配内存,这样的话,效率将会大大的提升。
    额外分配未使用空间数量:
    1). 如果修改之后SDS的长度小于1MB,那么程序将会分配和当前字符串len相同的空间给该字符串对象。比如说,wy和xx进行合并,这里是4个字符,大小为4,实际的存储大小为5,但是分配的内存空间大小会采用预分配的方式,那么分配后的内存大小为4+4+1=9个字节。
    2). 如果修改之后的SDS的长度大小大于等于1MB的话,程序分配的内存空间将会为1MB,比如说变化后的字符串对象达到了30M,当他在分配空间的时候只分配1MB空间。那么最终空间大小为30M+1MB+1B.
  4. 惰性释放
    当字符串进行缩短操作的时候,并不立即将空间释放出来,而是,将这部分空间通过free进行标识,本字符串有多少的空余的空间。这样的话,在再次使用时也可以避免分配内存造成的时间开销。
    当然,Redis中提供了专门的API,需要的时候,会真正的释放这部分空闲的内存。
  • 二进制安全
    C字符串必须符合某种编码,除了字符串的末尾外,不能包含空字符,否则会被误认为是字符串的结尾,导致最终读取的字符串是不完整的。这些限制导致了字符串不能用于存放图片、音频、视频等二进制数据,只能存放文本数据。
    但是在Redis中,不是靠空字符来判断字符串的结束的,而是通过len这个属性。那么,即便是中间出现了空字符对于SDS来说,读取该字符仍然是可以的。
    二进制安全.png
  • C字符串和SDS字符串区别总结
C字符串 SDS字符串
获取字符串长度复杂度 O(N) 获取字符串长度复杂度 O(1)
API是不安全的,可能造成缓冲区溢出 API 安全,不会造成缓冲区溢出
修改字符串长度N次需要修改N次内存 修改字符串长度N次最多需要修改N次内存
只能保存文本数据 可以保存二进制数据
可以使用所有<string.h>库函数 可以使用部分<string.h>库函数
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Redis数据库里面的每个键值对(key-value pair)都是由对象(object)组成的: 其中,数据库键...
    one_zheng阅读 551评论 0 0
  • Redis使用的是自己构建的简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将...
    但莫阅读 505评论 0 0
  • 一、SDS介绍 Redis没有使用C语言传统的字符串表示(以空字符结尾的字符串数组,以下简称C字符串),而是自己构...
    Vic_is_new_Here阅读 3,204评论 0 1
  • 简介   Redis 没有直接使用C语言的字符串表示,而是构建了一种称为简单动态字符串(Simple Dynami...
    阳光课代表阅读 1,354评论 0 0
  • 四时田园杂兴 宋 范成大 高田二麦接山青,傍水低田绿未耕...
    llllllHllllll阅读 217评论 0 0