Redis源码解析01: 简单动态字符串SDS

Redis没有直接使用C字符串(以’\0’结尾的字符数组),而是构建了一种名为简单动态字符串( simple  dynamic  string, SDS)的抽象类型,SDS设计API实现对字符串的各种修改。

1:SDS的定义

  在sds.h中,定义了结构体sdshdr表示SDS,其定义如下:

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

  len记录SDS保存的字符串的长度(不包括末尾的‘\0‘);free记录buf中未使用的字节数量(也不包括’\0‘);buf是字节数组,用于保存字符串。比如下面的例子:  

  

  结合上图,很好理解free,len,buf字段的意义

  sds也提供了查询sds实例free,len的接口,这些接口可以在O(1)复杂度的情况下查询字符串的长度、未使用空间。

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;
}  

2:SDS与C字符串的区别

C字符串不记录自身的长度信息,获取一个C字符串的长度的时间复杂度为O(N)。SDS在len属性中直接记录了字符串的长度,所以获取一个SDS字符串长度的事件复杂度是O(1)。这确保获取字符串长度的工作不会成为Redis的性能瓶颈。即使对一个非常长的字符串键反复执行”strlen”命令,也不会对系统性能造成任何影响。

C字符串不记录长度带来的另一个问题是容易造成缓冲区滋出。比如strcat函数将src字符串中的内容拼接到dest字符串的末尾, 如果dst的长度不足以容纳src,就会产生缓冲区滥出。

  与C字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS的API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。

sds sdscatlen(sds s, const void *t, size_t len)
{
    struct sdshdr *sh;
    size_t curlen = sdslen(s);  

    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    sh = (void*) (s-(sizeof(struct sdshdr)));
    memcpy(s+curlen, t, len);
    sh->len = curlen+len;
    sh->free = sh->free-len;
    s[curlen+len] = ‘\0‘;
    return s;
}  

sds sdscat(sds s, const char *t)
{
    return sdscatlen(s, t, strlen(t));
}  

  sdscat是通过sdscatlen实现的,在sdscatlen中,首先用sdsMakeRoomFor保证SDS具有足够的空间(sdsMakeRoomFor的函数实现见下面),然后才是将字符串t追加到s中。其他所有修改SDS的API都会通过sdsMakeRoomFor保证缓冲区不会溢出。

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_MAX_PREALLOC的值就是1024*1024,也就是1M。参数addlen表示需要扩容的长度。

  可以发现对SDS空间拓展的时候分两种情况:  

  如果对SDS进行修改之后,SDS的长度小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时SDS的 len属性的值将和free属性的值相同。

如果对SDS进行修改之后,SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。比如,如果进行修改之后,SDS的len将变成30MB,那么程序会分配1 MB的未使用空间,SDS的buf数组的实际长度将为30MB+1MB+1byte。

  通过在修改SDS之前调用sdsMakeRoomFor函数,确保不会出现溢出的问题。

  

原文地址:https://www.cnblogs.com/lovelaker007/p/8676101.html

时间: 2024-10-11 07:50:55

Redis源码解析01: 简单动态字符串SDS的相关文章

Redis源码阅读一:简单动态字符串SDS

源码阅读基于Redis4.0.9 SDS介绍 redis 127.0.0.1:6379> SET dbname redis OK redis 127.0.0.1:6379> GET dbname "redis" 从上面的例子可以看到,key为dbname的值是一个字符串"redis" Redis源码是用c写成,但并没有使用c的字符串.c的字符串有以下缺点: 没有储存字符串长度的变量,获取长度只能靠遍历字符串 扩容麻烦.没有相应保护,容易造成缓冲区溢出 更

redis源码学习_简单动态字符串

SDS相比传统C语言的字符串有以下好处: (1)空间预分配和惰性释放,这就可以减少内存重新分配的次数 (2)O(1)的时间复杂度获取字符串的长度 (3)二进制安全 主要总结一下sds.c和sds.h中的关键函数 1.sdsmapchars 1 /* Modify the string substituting all the occurrences of the set of 2 * characters specified in the 'from' string to the corresp

Redis源码解析——双向链表

相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的.教科书般简单的库.但是作为Redis源码的一部分,我决定还是要讲一讲的.(转载请指明出于breaksoftware的csdn博客) 基本结构 首先我们看链表元素的结构.因为是双向链表,所以其基本元素应该有一个指向前一个节点的指针和一个指向后一个节点的指针,还有一个记录节点值的空间 typedef struct listNode { struct listNode *prev; struct listNode *next;

redis源码解析之内存管理

zmalloc.h的内容如下: 1 void *zmalloc(size_t size); 2 void *zcalloc(size_t size); 3 void *zrealloc(void *ptr, size_t size); 4 void zfree(void *ptr); 5 char *zstrdup(const char *s); 6 size_t zmalloc_used_memory(void); 7 void zmalloc_enable_thread_safeness(v

Redis源码解析之ziplist

Ziplist是用字符串来实现的双向链表,对于容量较小的键值对,为其创建一个结构复杂的哈希表太浪费内存,所以redis 创建了ziplist来存放这些键值对,这可以减少存放节点指针的空间,因此它被用来作为哈希表初始化时的底层实现.下图即ziplist 的内部结构. Zlbytes是整个ziplist 所占用的空间,必要时需要重新分配. Zltail便于快速的访问到表尾节点,不需要遍历整个ziplist. Zllen表示包含的节点数. Entries表示用户增加上去的节点. Zlend是一个255

redis源码解析之事件驱动

Redis 内部有个小型的事件驱动,它主要处理两项任务: 文件事件:使用I/O多路复用技术处理多个客户端请求,并返回执行结果. 时间事件:维护服务器的资源管理,状态检查. 主要的数据结构包括文件事件结构体,时间事件结构体,触发事件结构体,事件循环结构体 /* File event structure */ typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE) */ aeFileProc *rfileProc

redis源码解析之dict数据结构

dict 是redis中最重要的数据结构,存放结构体redisDb中. typedef struct dict { dictType *type; void *privdata; dictht ht[2]; int rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict; 其中type是特定结构的处

Redis底层探秘(一):简单动态字符串(SDS)

redis是我们使用非常多的一种缓存技术,他的性能极高,读的速度是110000次/s,写的速度是81000次/s.这么高的性能背后,到底是怎么样的实现在支撑,这个系列的文章,我们一起去看看. redis的底层数据结构有以下7种,包括简单动态字符串(SDS),链表.字典.跳跃表.整数集合.压缩列表.对象.今天我们一起看下简单动态字符串(simple dynamic string),后面的文章以SDS简称. SDS简介 Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符串数组,以下简称

Redis源码阅读笔记(1)——简单动态字符串sds实现原理

首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里.柔性数组成员不占用结构体的空间,只作为一个符号地址存在,而且必须是结构体的最后一个成员.柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组.C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员.柔性数组成员允许结构中包