redis源码阅读: sds.c/sds.h (一)

最近开始阅读redis的源码,记录一下,也是给自己一个坚持读下去的动力。

sds

sds(Simple Dynamic String,简单动态字符串),是redis底层使用的字符串表示,取代了C语言中默认的char *类型。与C语言的默认字符串相比,sds既可以高效地实现追加和长度计算,同时也是二进制安全的,并会在字符串末尾自动添加'\0'。

sds的实现

typedef char *sds;

struct sdshdr {
    unsigned int len;  //已使用的长度
    unsigned int free; //剩余的长度
    char buf[];        //实际保存字符串数据的地方
};

其中,sizeof(struct sdshdr)= 2*sizeof(unsigned int),因为 char buf[ ] 等价于 char buf[0], 它仅对编译器有效,并不实际占用存储。

那么,一个sds字符串申请的实际内存为:sizeof(sdshdr)+len+free+1,其中的“1”实际上是sds自动添加的字符'\0'。


基本方法

1、sdslen( )

这个方法用来获取sds字符串长度。

/* 获取sds字符串长度
 *
 * s存的是buf首个char数据的地址
 *
 * sizeof(struct sdshdr) 8个字节,free+len
 *
 *   +--------------------------------+
 *   |          struct sdshdr         |
 *   +----------+----------+----------+
 *   |    len   |   free   |    buf   |
 *   +----------+----------+----------+
 *   |                     |
 *   |                     sds s
 *   |
 *   s-(sizeof(struct sdshdr))
 *
 */
static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

参数s存储的是buf[ ]的首个char数据的地址,而通过sds字符串s来获取对应的结构体sdshdr的地址的获取方法是:

struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));

即将指针向前移动到 sdshdr 的起点,从而得出一个指向 sdshdr 结构的指针。

2、sdsavail( )

这个方法用来获取sds字符串可用长度。

/*  获取sds可用长度 */
static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

sds创建函数

1、sdsnewlen( )

这个方法可以创建指定长度的sds,并接受C字符串为初始化内容。

sds sdsnewlen(const void *init, size_t initlen) {
//    size_t在32位架构上是4字节,在64位架构上是8字节
//    无符号数,表示0-MAXINT的范围
    struct sdshdr *sh;

    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    sh->len = initlen;
    sh->free = 0;
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    return (char*)sh->buf;
}

当执行下面这行代码:

mystring = sdsnewlen("abc",3);

会在堆内存上面开辟一段连续的空间,具体的空间大小是11个字节的空间,其中的八个字节用于存放sdshdr结构体,三个字节用于存放字符串“abc”,最后的一个字节用于存放一个隐式的字符‘\0’。

另外,你可以使用printf()打印字符串,因为字符串末尾有一个隐式\0;但是字符串是二进制安全的,并且中间可以包含\0字符,因为长度存储在sds头中。

2、 sdsempty( )

这个方法用于创建一个空的sds字符串。

sds sdsempty(void) {
    return sdsnewlen("",0);
}

即使在这种情况下,字符串也始终具有隐式空项 '\0'。

3、sdsnew( )

这个方法可以根据给定的C字符串创建sds字符串。

sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

sds操作函数

1、sdsdup( )

这个方法可以复制sds字符串。

sds sdsdup(const sds s) {
    return sdsnewlen(s, sdslen(s));
}
2、sdsfree( )

这个方法用于释放sds字符串,如果字符串 's' 为NULL,则不执行任何操作。

void sdsfree(sds s) {
    if (s == NULL) return;
    zfree(s-sizeof(struct sdshdr));
}
3、sdsupdatelen( )

这个方法用来修改字符串长度。

void sdsupdatelen(sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    int reallen = strlen(s);
    sh->free += (sh->len-reallen);
    sh->len = reallen;
}

如下例所示:

s = sdsnew("foobar");
s[2] = '\0';
sdsupdatelen(s);
printf("%d\n", sdslen(s));

代码的输出将是“2”,但是如果我们注释掉对sdsupdatelen的调用,则输出将为“6”,因为字符串被修改但逻辑leng仍然是6个字节,sds字符串是二进制安全的,可以包含任何字符(字节),包括字符 ‘\0’ 。

4、sdsclear( )

这个方法用于修改sds字符串以使其为空(零长度)。

void sdsclear(sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    sh->free += sh->len;
    sh->len = 0;
    sh->buf[0] = '\0';
}

但是,这个方法不会使sds字符串丢弃所有现有缓冲区,而是将其设置为可用空间,以便下一个追加操作直到先前可用的字节数前,都不需要分配。

5、sdsMakeRoomFor( )

这个方法用于对 sds 字符串 s 所对应 sdshdr 结构体的 buf 进行扩展。

sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;

    if (free >= addlen) return s;
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
//    在小于SDS_MAX_PREALLOC(即1M)时,会预分配出多一倍的空间,
//    在大于该阈值时,每次只预分配多SDS_MAX_PREALLOC内存。

    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
    if (newsh == NULL) return NULL;

    newsh->free = newlen - len;
    return newsh->buf;
}

这个方法的目的在于放大sds字符串末尾的可用空间,即确保在调用此函数之后可以在字符串结束后覆盖 addlen 字节,再加上 '\0' 一个字节。

但是,这个方法不会更改sdslen()返回的sds字符串的 *length,而只会更改我们拥有的空闲缓冲区空间。

注意,代码中的 SDS_MAX_PREALLOC 是定义在 sds.h 中的:

 #define  SDS_MAX_PREALLOC  (1024*1024)

其大小是1M,并根据这个值,在sds字符串扩容时进行空间预分配:

扩容过程中,如果需求长度小于1MB,则增加双倍长度,如果大约1MB,则需求长度+1MB,通过此策略,减少内存重分配次数。
 if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
6、sdsAllocSize( )

这个方法返回指定的sds字符串分配的总大小。

size_t sdsAllocSize(sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    return sizeof(*sh)+sh->len+sh->free+1;
}

返回值的大小,包括:

  • 1)指针前的sds头。
  • 2)字符串。
  • 3)最后的空闲缓冲区(如果有的话)。
  • 4)隐式空项(‘\0’)。
7、sdsIncrLen( )

这个方法用于对给定 sds 字符串的 buf 的右端进行扩展或缩小。

void sdsIncrLen(sds s, int incr) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    if (incr >= 0) //增加字符串长度
        assert(sh->free >= (unsigned int)incr);
//      assert(): 其作用是如果它的条件返回错误,则终止程序执行
    else           //减少字符串长度
        assert(sh->len >= (unsigned int)(-incr));
    sh->len += incr;
    sh->free -= incr;
    s[sh->len] = '\0';
}

它根据 incr 的大小增加 sds 字符串的长度并减少字符串末尾的左边空闲空间,同时,在字符串的新结尾设置 '\0'。

8、sdsgrowzero( )

这个方法可以使 sds 字符串增长到指定的长度,并将不属于 sds 字符串原始长度的字节将设置为零。

sds sdsgrowzero(sds s, size_t len) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    size_t totlen, curlen = sh->len;

    if (len <= curlen) return s;
    s = sdsMakeRoomFor(s,len-curlen);
    if (s == NULL) return NULL;

    /* Make sure added region doesn't contain garbage */
    sh = (void*)(s-(sizeof(struct sdshdr)));
    memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
    totlen = sh->len+sh->free;
    sh->len = len;
    sh->free = totlen-sh->len;
    return s;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容