redis源码分析(3)sds

转载 2014年08月27日 20:23:38

原文来自:http://www.cnblogs.com/kernel_hcy/p/3366112.html

sds是redis中用来处理字符串的数据结构。sds的定义在sds.h中:

typedef char *sds;

简洁明了!简明扼要!(X,玩我呢是吧!这特么不就是c中的字符串么?!)。像redis这种高端大气上档次的服务器显然不会这么的幼稚。在sds的定义之后,还有一个结构体:

struct sdshdr {
     int len;
     int free;
     char buf[];
}

有len,有free,这就有点意思了。很明显,根据这个结构体的定义,这是sds的header,用来存储sds的信息。注意最后的buf定义,这个buf数组没有设置长度。这是为神马呢?在gcc中,这种方式可以使得buf成为一个可变的数组,也就是说,可以扩展buf同时又保证在使用的时候,感觉buf始终在struct sdshdr中。有点啰嗦,其实可以用下图展示:

  sdshdr       sds
    |           |
    V           V
    ----------------------------
    |len | free | buf …        |
    ----------------------------     

这个就是sds的内存分布图。struct sdshdr这个结构体放在了真正的数据之前,且是紧挨着的。这样,通过buf引用的数组其实就是后面的数据。这个是利用了c中数组访问的特点。
下面我们来看看如何创建一个sds:

/* Create a new sds string with the content specified by the 'init' pointer
 * and 'initlen'.
 * If NULL is used for 'init' the string is initialized with zero bytes.
 *
 * The string is always null-termined (all the sds strings are, always) so
 * even if you create an sds string with:
 *
 * mystring = sdsnewlen("abc",3");
 *
 * You can print the string with printf() as there is an implicit \0 at the
 * end of the string. However the string is binary safe and can contain
 * \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) {
    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;
}

重点是这句(zcalloc也一样,只是分配内存的时候顺带初始化为0):

sh = zmalloc(sizeof(struct sdshdr)+initlen+1)

创建一个sds的时候,实际申请的内存大小为sdshdr的大小,加上调用者希望的sds的大小,再加一。另外,zmalloc的返回值直接赋值给了sh,sh是struct sdshdr。那么,在创建一个sds的时候,将sds的struct sdshdr放到了真正的数据的前面,这样可以通过buf引用到后面的数据。多加一个一是为了保证有地方放'\0'。根据注释,sds默认以'\0'结尾,且可以存放二进制的数据,因为struct sdshdr中存放了数据的长度。在sdsnewlen的最后,返回的是(char\*)sh->buf,也就是说sds实际指向的就是一个char\*数组。**所有可以对char\*的操作也同时可以操作sds**。

那sds的长度等信息如何获取呢?看下面的代码:

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

这两个函数分别是获取sds的实际长度和可用空间。核心代码就是这句:

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

将sds的地址减去struct sdshdr的长度然后赋值给sh,这就得到了sds对应的struct sdshdr。根据前面的内存分布图,struct sdshdr始终是在数据的前面,一次很容易得到struct sdshdr的地址。得到了struct sdshdr的地址之后,其他的就很简单了。

sds支持动态的扩展空间,sdsMakeRoomFor这个函数用来扩展sds的空间:

/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 * 
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
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;
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
    if (newsh == NULL) return NULL;

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

这个函数保证sds至少有addlen长度的空间可用。这个函数体现了sds的空间扩展策略。如果有足够的空间,则直接返回。如果空间不够,当len+addlen小于SDS_MAX_PREALLOC时,将空间扩展到(len+addlen)\*2。当len+addlen大于SDS_MAX_PREALLOC,将空间扩展到len+addlen+SDS_MAX_PREALLOC。sds的扩展考虑了实际需要的空间大小,扩展的效率要高一些。如果每次扩大原来的二倍,当需要的空间大于初始空间二倍时,需要多次的扩展操作,也就意味着多次的zrealloc操作。sds的扩展可以在任何情况下一次扩展到位。


sds最大的特点就是所有可以对char\*的操作都可以操作sds,这在实际使用sds的的时候可以带来很多方便。比如,从socket中读取数据存储到sds中,可以如下操作:

 /* sds s */
 int oldlen = sdslen(s);
 s = sdsMakeRoomFor(s, BUFFER_SIZE);
 nread = read(fd, s+oldlen, BUFFER_SIZE);
 sdsIncrLen(s, nread);

在调用read的时候,可以把sds看做是char\*来处理(实际上sds就是char\*)。当然,最后一定要调用sdsIncrLen来修正sds的长度。

Redis源码分析(四)-- sds字符串

今天分析的是Redis源码中的字符串操作类的代码实现。有了上几次的分析经验,渐渐觉得我得换一种分析的方法,如果每个API都进行代码分析,有些功能性的重复,导致分析效率的偏低。所以下面我觉得对于代码的分...
  • Androidlushangderen
  • Androidlushangderen
  • 2014年10月08日 20:29
  • 5926

Redis深入理解-数据结构篇(1)-简单动态字符串SDS

Redis没有直接使用C语言中的字符串,而是自己构建了SDS这样的一种简单动态字符串,并且将他作为Redis中字符串的默认的表示,个人认为,Redis并未完全抛弃C语言字符串,只不过是在C语言字符串的...
  • wangyang1354
  • wangyang1354
  • 2016年10月03日 13:02
  • 2608

【Redis源码剖析】 - Redis内置数据结构之字符串sds

今天花了一个晚上的时间分析了Redis中字符串操作的实现,源文件为sds.h和sds.c。...
  • Xiejingfa
  • Xiejingfa
  • 2016年03月28日 21:07
  • 4129

Redis源码剖析和注释(二)--- 简单动态字符串

Redis 简单动态字符串1.介绍Redis兼容传统的C语言字符串类型,但没有直接使用C语言的传统的字符串(以’\0’结尾的字符数组)表示,而是自己构建了一种名为简单动态字符串(simple dyna...
  • men_wen
  • men_wen
  • 2017年04月06日 16:39
  • 932

Redis内部数据结构详解之简单动态字符串(sds)

本文所引用的源码全部来自Redis2.8.2版本。 Redis中简单动态字符串sds数据结构与API相关文件是:sds.h, sds.c。 转载请注明,本文出自:http://bl...
  • Acceptedxukai
  • Acceptedxukai
  • 2013年12月22日 13:54
  • 7839

redis源码分析-sds字符串

介绍 等待 简易动态的sds字符串 1.介绍 在c语言中,一般使用char*定义字符串类型,但redis却采用sds结构保存字符串。那么redis为什么弃用char而改用sds呢?这样做是基...
  • Mijar2016
  • Mijar2016
  • 2016年07月04日 21:25
  • 239

Redis源码分析(sds)

一、SDS简介sds (Simple Dynamic String),Simple的意思是简单,Dynamic即动态,意味着其具有动态增加空间的能力,扩容不需要使用者关心。String是字符串的意思。...
  • yangbodong22011
  • yangbodong22011
  • 2017年11月01日 23:09
  • 829

Redis源码分析——SDS

Redis中基于C字符串构建了一种新的简单动态字符串SDS,作为对象的一种底层实现方式。它保存了C风格字符串以’\0’结尾的惯例,可以使用C字符串函数。由于SDS中保存了字符串的长度,所以可以以O(1...
  • xijiacun
  • xijiacun
  • 2016年04月24日 22:59
  • 195

redis-SDS 分析

SDSSDS是 redis 内置的字符串对象,redis没有使用C 自带的字符串结构,而是自己实现 字符串的表示结构。SDS 数据结构定义在 sds.h中定义了 sds的结构 - len 表示字符串...
  • zhuoxiuwu
  • zhuoxiuwu
  • 2017年07月13日 00:05
  • 246

底层实现-SDS 简单动态字符串

一 介绍 Redis只会使用C字符串作为字面量,大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态字符串)作为字符串表示。也就是说,Redis中...
  • f88520402
  • f88520402
  • 2016年05月30日 16:53
  • 659
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:redis源码分析(3)sds
举报原因:
原因补充:

(最多只允许输入30个字)