码迷,mamicode.com
首页 > 其他好文 > 详细

redis底层数据结构之sds

时间:2016-05-04 06:42:17      阅读:231      评论:0      收藏:0      [点我收藏+]

标签:字符串   redis   sds   


最近,我想通过redis的源码来学习redis。虽然平时工作中用得不多,不过对redis还是比较感兴趣的,毕竟它的性能是不错的。redis是一个开源的项目,我们可以通过源代码去了解redis。我后面会通过自己的学习,写一些关于redis源码的帖子。帖子的主要内容是分析代码设计,而并不会对源码进行详细解说。如果有不对的地方,请指正。源码是reids 3.0.3版本。

sds

一、redis的字符串 sds:

c语言的字符串是 char *,redis则自己定义了内部的字符串sds,同时也提供了一组sds的相关的操作函数,redis这样做是为了更方便地使用字符串。先看看sds的定义:

typedef char *sds;
struct sdshdr {
    unsigned int len;    //字符串长度
    unsigned int free;    //剩余空间大小
    char buf[];        //字符串存储地址
};

先看 struct sdshdr,这是redis字符串的头部信息,各字段意思见注释。这样的字符串设计,是与字符串的相关操作关联的。为更好地了解sds,先举个例子看一下sds是如何的存储“hello world”的。下面一一个例子:

len = 11
free = 5
buf[] = “hello world” + ’\0’ + 5个byte空间

其中 len=11 指的是字符串的长度,free=5是指sds还剩余的存储空间,buf的实际大小为 len + 1 + free,其中 1 是指 ‘\0’ 的空间大小。

从数据结构的定义和上面例子可以看出:

    1. len是字符串的长度,通过记录下长度,对于求字符串长度的操作是O(1)的,但这样也带来了len的维护问题,在更新字符串长度时,同时需要更新len字段。

    2. free是剩余空间,剩余的空间是用于扩展字符串长度。但是剩余的空间是有限的,为保证字符串长度能自由增长,需要一些方法去增加剩余的空间。

    3. buf字符串存储的地址,把buf声明为0长度的char数组,是常用的内容空间分配设计方法。一般情况下,这样的数据结构都是动态申请空间的。这样就能在动态申请空间时,分配 数据结构本身的大小+buf大小 的空间,做到动申请,现时也能通过重新申请的方法扩展空间。具体可见 sdsnewlen 和 sdsMakeRoomFor 函数的实现。

    4. ’\0’,字符串后面总是以‘\0’结尾,目的是让sds的buf跟c语言的字符串对齐,能够通过printf等函数直接输出。但这里需要注意‘\0’并不能代表sds字符串的结束,因为sds字符串中也可能含有’\0’字符。字符串的结束只能由len指定。这样设计的字符串,不仅可以支持c语言的字符串,还可以扩展到二进制串,就像std::string一样。

    5. 支持二进制存储。由于sds的字符串长度是由len指定的,字符串中的每一个byte,都可以覆盖二进制中的所有值,所以可以支持二进制存储。redis为什么要支持二进制串的存储呢?我想是因为redis需要间接支持复杂数据结构的存储。因为redis本身只提供了string,list,set,zset,hash等通用的数据结构的存储,对于用户自己定义的数据结构,redis并不能直接支持其存储。而redis提供了二进制的存储,可以让用户自己把复杂的数据结构序列化成二进制串再存储。这样虽然在存时需要序列化、取时需要反序列化,消耗了CPU,但却换来的存储的通用性。

    6. typedef char * sds。sds定义为char*而不是sdshdr*。主要的原因是为了使sds看起来更像char*,在使用一些函数时能跟char*无区别,如printf。这样做也是有代价的,那便是sds相关操作的复杂性提高了。几乎每个有关sds的操作,都要通过sds(char*)来定位出sdshdr*。

sds也有一些缺点:

sdshdr占用一定空间
字符串中有剩余空间,虽然可用于扩展,但也可能是一种浪费。
需要代码本身提供一组相关的操作函数,才能更好地使用。
使用sds时,需要比较了解sds的特点和其操作函数的使用规范,对编程者要求比较高。


二、sds的相关操作函数:


为了让使用者更方便地使用sds,redis提供了一系列关于sds的操作函数。如:

sdslen 字符串长度
sdsavail 字符串可用空间长度(free)
sdsnew 创建字符串
sdsdup 复制字符串
sdsfree 释放字符串
sdscat 字符串拼接
…

这里不一一列举。
sds和其操作函数,一同为redis提供了操作方便的字符串,但同时也增加了字符串操作的复杂性。在使用sds及其操作函数时,必须了解sds的行为特点才能正确使用。

举一个函数来分析,sdsMakeRoomFor:

/* 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的空间,以够字符串长度增加addlen。
 * 如果字符串本身的剩余空间大于addlen,则无需增加sds的空间,否则增加空间。
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;
    /* 如果剩余的空间足够存储addlen,无需增加空间 */
    if (free >= addlen) return s;
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));    /* 计算sdshdr地址 */
    newlen = (len+addlen); /* 字符串增加后的总长度 */
    /* 预分配空间的策略,如果新的长度小于 SDS_MAX_PREALLOC(1M),
     则预分配多一倍的长度,否则预分配多SDS_MAX_PREALLOC大小的空间 */
    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;
}


上面函数 sdsMakeRoomFor 里面会有预分配空间的策略,需要注意的是,预分配空间可以预留空间给字符串增加,但同时也会造成浪费。并不是所有的字符串都会有预分配空间,只有 sdsgrowzero,sdscatlen,sdscpylen,sdscatfmt 等函数会调用 sdsMakeRoomFor。

本文出自 “chhquan” 博客,请务必保留此出处http://chhquan.blog.51cto.com/1346841/1769854

redis底层数据结构之sds

标签:字符串   redis   sds   

原文地址:http://chhquan.blog.51cto.com/1346841/1769854

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!