Redis源码-数据结构之sds字符串

国庆节除了陪伴吕友,花了差不多两天时间初步了解了一下Redis,接下来一段时间,将深入阅读Redis源码,也做一个记录,看一个源码,我觉得还是先从基础的模块看起,比如,数据结构,Redis中的实现了很多的数据结构,当然,很多开源项目也是自己实现各种数据结构以满足定制需求,我首先阅读的是底层字符串模块-SDS

sds的定义:

typedef char *sds;

说白了就是字符串的别名,整个Redis的字符串用的就是这个,但是内部使用了一个结构来维护这个sds

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

之所以这样设计,是为了符合Redis的某一些特性,总的来说,sds相比于C语言的字符串具有以下几个优点:

1.获取字符串长度的复杂度为O(1),因为内部维护了一个保存字符串长度的len

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

要想访问到len和free字段,还要把指针直到结构体开始出,当时看到这里还犹豫了一下,基础知识不行— —!

2.是二进制安全的

这里要解释一下什么是二进制安全,C语言中的字符串采用ASCII编码表示,但是这里我们用的是一个字符数组,怎么存进去的,拿出来还是什么,例如:test\0test\0,那么len就会是9,读出来也是test\0test\0,而不是说读到第一个结束符就结束了

    sds s = sdsnewlen("test", 5);
    printf("%s\n", s);
    size_t len = sdslen(s);
    printf("%zu\n", len);
    size_t free = sdsavail(s);
    printf("%zu\n", free);

可以得出得到len=5

源码:

/* 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 = malloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = calloc(1,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;
}

3.不会出现缓冲区溢出,利用free字段可以减少修改字符串长度导致的内存重新分配

我们知道,C语言中的这两组函数如果使用不当,是会造成缓冲区溢出的

char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n);
The  strcat()  function  appends the src string to the dest string, overwriting the null byte ('\0') at the end of dest, and then adds a terminating null byte.  The strings may not overlap, and the dest string must have enough space for the result.
The strncat() function is similar, except that
*  it will use at most n characters from src; and
*  src does not need to be null terminated if it contains n or more characters.
As with strcat(), the resulting string in dest is always null terminated.

而sds中的cat实现如下:

/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
 * end of the specified sds string 's'.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
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;
}

那么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 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 = realloc(sh, sizeof(struct sdshdr)+newlen+1);
    if (newsh == NULL) return NULL;

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

可以看出sds对于字符串长度修改有这样一个规则,如果

AddLen < free 使用free

AddLen > free && AddLen < 1M free = len

AddLen > free && AddLen > 1M free = 1M

时间: 2024-10-10 11:12:37

Redis源码-数据结构之sds字符串的相关文章

redis源码分析(一)-sds实现

redis支持多种数据类型,sds(simple dynamic string)是最基本的一种,redis中的字符串类型大多使用sds保存,它支持动态的扩展与压缩,并提供许多工具函数.这篇文章将分析sds在redis中是如何实现的. 1.    sds类型 sds在redis中其实就是一个char*类型的别名,声明如下: typedef char *sds; 但是,以sds指向的字符串的存储格式具有一定的规则,即在字符串数据之前存储了相应的头部信息,这些头部信息包含了:1. alloc-分配的内

Redis源码-数据结构之Adlist双端链表

Redis的Adlist实现了数据结构中的双端链表,整个结构如下: 链表节点定义: typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; } listNode; 链表定义: typedef struct list { listNode *head; listNode *tail; void *(*dup)(void *ptr); void (*free)(void *ptr); i

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

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

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

今天分析的是Redis源码中的字符串操作类的代码实现.有了上几次的分析经验,渐渐觉得我得换一种分析的方法,如果每个API都进行代码分析,有些功能性的重复,导致分析效率的偏低.所以下面我觉得对于代码的分析偏重的是一种功能整体的思维实现来讲解,其中我也会挑出一个比较有特点的方法进行拆分了解,这也可以让我们见识一下里面的一些神奇的代码.好,回归正题,说到字符串,这不管放到哪个编程语言中,都是使用频率极高的操作类.什么new String, concat, strcopy,substr, splitSt

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源码学习:字符串

Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串,还用来当做缓冲区,例如AOF缓冲区或输入缓冲区等.如下所示,整数len和free分别表示buf数组中已使用的长度和剩余可用的长度,buf是一个原生C字符串,以\0结尾. sds就是sdshdr中char buf[]的别名,后面能看到,各种操作函数的入参和返回值都是sds而非sdshdr.那sdshd

Redis源码解析——字符串map

本文介绍的是Redis中Zipmap的原理和实现.(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结构包含一个头信息.一系列字符串对(之后把一个"字符串对"称为一个"元素"(ELE))和一个尾标记.用图形表示该结构就是: Redis源码中并没有使用结构体来表达该结构.因为这个结构在内存中是连续的,而除了HEAD和红色背景的尾标记END(恒定是0xFF)是固定的8位,其

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 源码?ZZ

原文链接 在这篇文章中, 我将向大家介绍一种我认为比较合理的 Redis 源码阅读顺序, 希望可以给对 Redis 有兴趣并打算阅读 Redis 源码的朋友带来一点帮助. 第 1 步:阅读数据结构实现 刚开始阅读 Redis 源码的时候, 最好从数据结构的相关文件开始读起, 因为这些文件和 Redis 中的其他部分耦合最少, 并且这些文件所实现的数据结构在大部分算法书上都可以了解到, 所以从这些文件开始读是最轻松的.难度也是最低的. 下表列出了 Redis 源码中, 各个数据结构的实现文件: 文