redis源码分析(二)、sds动态字符串学习总结

sds字符串

Redis 只会使用 C 字符串作为字面量, 在大多数情况下, Redis 使用 SDS (Simple Dynamic String,简单动态字符串)作为字符串表示。 比起 C 字符串, SDS 具有以下优点:

  1. 常数复杂度获取字符串长度。
  2. 杜绝缓冲区溢出。
  3. 减少修改字符串长度时所需的内存重分配次数。
  4. 二进制安全。
  5. 兼容部分 C 字符串函数。

根据传统, C 语言使用长度为N+1的字符数组来表示长度为 N 的字符串,并且字符数组的最后一个元素总是空字符 '\0' 。

C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求, 本节接下来的内容将详细对比 C 字符串和 SDS 之间的区别, 并说明 SDS 比 C 字符串更适用于 Redis 的原因。

SDS又叫简单动态字符串,在Redis中默认使用SDS来表示字符串。比如在Redis中的键值对中的键一般都是使用SDS来实现。首先需要说明的是在Redis中,字符串不是用传统的字符串来实现,而是Redis自己构建了一个结构来表示字符串。优点如下:

1、O(1)时间内获取字符串长度。常数复杂度获取字符串长度(依据其结构特性,只需要访问其结构体成员len既可获得字符串长度)

2、杜绝缓冲区溢出,另外SDS还提供的一些API操作,是二进制安全的(也就是不会因为空格等特殊字符而中断字符串)、不会溢出(API操作会检查其长度)

3、减少了修改字符串时带来的内存重分配次数。

  • 对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1024*1024,则分配2倍的修改后的长度+1
  • 对于减少的字符串其并不立即释放空间,而是回归到alloc中去。(自动查阅redis内存分配与释放策略)
typedef char *sds;//底层用于存储字符串的数据结构SDS

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    //低三位保存type,高5位保存长度
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

从代码中可以看出,SDS表示的字符串是有SDS header和char*指针组成,而SDS的头部主要由四部分组成:

  • len:SDS字符串已使用的空间。
  • alloc:申请的空间,减去len就是未使用的空间,初始时和len一致。
  • flag:只使用了低三位表示类型,细化了SDS的分类,根据字符串的长度的不同选择不同的sds结构体,而结构体的主要区别是len和alloc的类型,这样做可以节省一部分空间大小,毕竟在redis字符串非常多,进一步的可以节省空间。
  • buf: 用了C的特性表示不定长字符串。

除了sdshdr5之外,其他结构都是相似的。我们先看看sdshdr,它只有flags和buf成员,其中flag空间被C充分利用,其第三位保存了SDS字符串的类型.

#define SDS_TYPE_MASK 7   // 类型掩码
#define SDS_TYPE_BITS 3    
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); // 获取header头指针
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))   // 获取header头指针
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 获取sdshdr5的长度,低三位保存了type
/*sds结构一共有五种Header定义,其目的是为了满足不同长度的字符串可以使用不同大小的Header,从而节省内存。 
Header部分主要包含以下几个部分:len、alloc、flags其中
len:表示字符串真正的长度,不包含空终止字和alloc以及flag
alloc:表示字符串的最大容量,不包含Header和最后的空终止字符和flag
flag:只用了3位表示sds的type 就是表示header的类型*/
// 五种header类型,flags取值为0~4
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

sdsnewlen函数

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) {
    void *sh;
    sds s;

    // 根据initlen 求sds使用的结构体类型
    char type = sdsReqType(initlen);

    //下面是我在测试的时候添加的打印字符串类型代码,源码中没有
    int type_t = type;
    printf("sds.c------->sdsnewlen: the sds type is: [ %d ]\n", type_t);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */

    //type5 不再使用 而是直接使用type8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;

    //sds使用的结构体的大小
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */  //flag的第三位表示sds使用的结构体

    //分配内层 大小是sds结构体的大小: hdrlen + buf的大小initlen + '\0' 
    sh = s_malloc(hdrlen+initlen+1);
    //init为null的时候  直接初始化sh内容
    if (!init)
        memset(sh, 0, hdrlen+initlen+1); 
    //sh 分配失败的情况直接返回null
    if (sh == NULL) return NULL;

    // s为数据部分的起始指针 指向buf地址指针
    s = (char*)sh+hdrlen;
    //fp指向flag  flag的低三位表示sds使用的结构体类型
    fp = ((unsigned char*)s)-1;

    //根据sds使用的结构体类型 给结构体中的成员赋值
    switch(type) {
        case SDS_TYPE_5: {
            // initlen << SDS_TYPE_BITS 把initlen的值保存到flag的高5位中去  低三位保存type的值
            *fp = type | (initlen << SDS_TYPE_BITS); 
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        //拷贝数据部分
        memcpy(s, init, initlen);  

    // 与C字符串兼容
    s[initlen] = '\0';  

    // 返回创建的sds字符串指针
    return s;
}

sdsMakeRoomFor函数

函数原型:sds sdsMakeRoomFor(sds s, size_t addlen)

  • 说明:实现扩充已有sds的可用空间为指定的大小,扩充规则是:当addlen的长度小于1024*1024时,则申请的空间是2*(addlen+len),否则扩充为1024*1024大小。
  • 返回值:扩充后的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) {
    void *sh, *newsh;
    size_t avail = sdsavail(s); //返回剩余可用的空间。
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK; //获取type
    int hdrlen;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s; //如果可用空间大于addlen直接返回旧的字符串

    len = sdslen(s); //求sds的长度
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    
    //根据newlen调整sds的type
    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    
    //根据type获取sds使用的结构体的长度
    hdrlen = sdsHdrSize(type); 
  
   //若类型和原有类型一样,则采用realloc分配空间,否则重新分配采用malloc函数分配空间
   if (oldtype==type) {       
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1); //把s中的字符串拷贝到newsh指向的buf中去
        s_free(sh);//释放旧的sh空间
        s = (char*)newsh+hdrlen; //s指向重新分配的字符串
        s[-1] = type; //指定newsds的type
        sdssetlen(s, len); //设置新的字符串的长度
    }
    sdssetalloc(s, newlen); //设置新的newsh的分配的空间长度
    return s;
}

**sdscatlen **:sds提供了字符串的连接函数,用来连接两个字符串

//sds字符串连接
sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);

    //扩展s的空间
    s = sdsMakeRoomFor(s,len); 
    if (s == NULL) return NULL;
    
    // 连接新字符串
    memcpy(s+curlen, t, len);

    // 设定连接后字符串长度
    sdssetlen(s, curlen+len);
    s[curlen+len] = '\0';
    return s;
}
sds sdsempty(void); // 清空sds
sds sdsdup(const sds s); // 复制字符串
sds sdsgrowzero(sds s, size_t len); // 扩展字符串到指定长度
sds sdscpylen(sds s, const char *t, size_t len); // 字符串的复制
sds sdscpy(sds s, const char *t); // 字符串的复制
sds sdscatfmt(sds s, char const *fmt, ...);   //字符串格式化输出
sds sdstrim(sds s, const char *cset);       //字符串缩减
void sdsrange(sds s, int start, int end);   //字符串截取函数
void sdsupdatelen(sds s);   //更新字符串最新的长度
void sdsclear(sds s);   //字符串清空操作
void sdstolower(sds s);    //sds字符转小写表示
void sdstoupper(sds s);    //sds字符统一转大写
sds sdsjoin(char **argv, int argc, char *sep);   //以分隔符连接字符串子数组构成新的字符串

sdsull2str 把一个long long的类型的数转成字符串

#include 
using namespace std;  
int sdsll2str(char *s, long long value) {
    char *p, aux;
    unsigned long long v;
    size_t l;

    /* Generate the string representation, this method produces
     * an reversed string. */
    v = (value < 0) ? -value : value;
    p = s;
    do {
        *p++ = '0'+(v%10);
        v /= 10;
    } while(v);
    if (value < 0) *p++ = '-';

    /* Compute length and add null term. */
    l = p-s;
    *p = '\0';

    /* Reverse the string. */
    p--;
    while(s < p) {
        aux = *s;
        *s = *p;
        *p = aux;
        s++;
        p--;
    }

//	std::cout << s << std::endl;
    return l;
}

  
int main()  
{  
	
	char str[10]={0};
	sdsll2str(str, 1234567);
	std::cout << str << std::endl;
}

你可能感兴趣的:(redis)