Redis5.0.9 一篇理解数据结构之Simple Dynamic Strings

 
Simple Dynamic Strings 动态字符串 是Redis 的基本数据类型之一,也是最常见的数据类型。
兼容了C语言标准字符串处理函数,在此基础上保证了二进制安全。

目录

1.常用命令

2.源码

1.初始化SDS

2.空间增加:字符串拼接

3.空间释放

 


1.常用命令

127.0.0.1:6379> get str
"helloWorld"
127.0.0.1:6379> TYPE str
string
127.0.0.1:6379> STRLEN str
(integer) 10
127.0.0.1:6379> 

2.源码

1.源码文件在redis/src/sds.h.  sds.c

  1. //创建sds
    typedef char *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 {
        unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
        char buf[];
    };
    struct __attribute__ ((__packed__)) sdshdr8 {
        uint8_t len; /* 表示字符串已使用的长度,buf 长度 */
        uint8_t alloc; /* 表示字符串的容量 */
        unsigned char flags; /* 低3位存储类型,高5位存储长度 */
        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[];
    };

    使用 __attribute__ ((__packed__))  关键字,告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行紧凑对齐,是GCC编译器特有语法。

  2. sds 字符串根据字符串的长度,区分了5种结构
函数名类型存取最大字符串长度
sdshdr5SDS_TYPE_532(2^5) 
sdshdr8SDS_TYPE_80xff(2^8-1)
sdshdr16SDS_TYPE_160xffff(2^16-1)
sdshdr32SDS_TYPE_320xffffffff(2^32-1)
sdshdr64SDS_TYPE_642^64 -1

1.初始化SDS

//创建初始化sds
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */

    sh = s_malloc(hdrlen+initlen+1);
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
    switch(type) {
        case SDS_TYPE_5: {
            *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);
    s[initlen] = '\0';
    return s;
}


static inline int sdsHdrSize(char type) {
    switch(type&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return sizeof(struct sdshdr5);
        case SDS_TYPE_8:
            return sizeof(struct sdshdr8);
        case SDS_TYPE_16:
            return sizeof(struct sdshdr16);
        case SDS_TYPE_32:
            return sizeof(struct sdshdr32);
        case SDS_TYPE_64:
            return sizeof(struct sdshdr64);
    }
    return 0;
}
 
  1. 1. 根据初始化长度获取SDS 的结构类型,如果长度为initlen == 0 ,直接设置SDS 类型为SDS_TYPE_8;

  2. 2.分配内存, 内存空间长度 = hdrlen+initlen+1 (头部长度+ buf 的长度 + 字符串最后空格’\0’)

  3. 3.根据SDS 类型初始化sds 头部,长度,标记,容量

  4. 4.复制数据memcpy

 

2.空间增加:字符串拼接

/* 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) {
    size_t curlen = sdslen(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;
}

/* Append the specified null termianted C string to the 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 sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}

/* Append the specified sds 't' to the existing sds 's'.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {
    return sdscatlen(s, t, sdslen(t));
}


sdscat 与  sdscatsds 都可以作为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 sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
   //获取目前s的可用空间
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

    //如果可用空间大于等于新增空间则直接return
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
   // 如果newlen 长度小于 SDS_MAX_PREALLOC  (1024*1024)长度则分配2倍所需空间长度
   // 否则 分配所需空间+ SDS_MAX_PREALLOC 长度
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);

    // 不再使用SDS_TYPE_5 类型,如果类型为SDS_TYPE_5  改用SDS_TYPE_8 类型
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {
        //sds 类型无变化,重新分配内存即可
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
       / *类型改变,由于标头大小发生变化,因此需要将字符串向前移动*,并且不能使用realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
       //数据复制
        memcpy((char*)newsh+hdrlen, s, len+1);
        //释放原始数据内存
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    } 
   //更新容量
    sdssetalloc(s, newlen);
    return s;
}

3.空间释放

/ *重新分配sds字符串,使其末尾没有可用空间。 字符串保持不变,但是接下来的串联操作将需要重新分配。 *在调用之后,传递的sds字符串不再有效,并且所有引用都必须替换为调用返回的新指针。 * /
sds sdsRemoveFreeSpace(sds s) {
    void *sh, *newsh;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
    size_t len = sdslen(s);
    size_t avail = sdsavail(s);
    sh = (char*)s-oldhdrlen;

   //可用空间长度为0 直接返回
    if (avail == 0) return s;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
    type = sdsReqType(len);
    hdrlen = sdsHdrSize(type);

    if (oldtype==type || type > SDS_TYPE_8) {
     //类型没变,重新分配内存
        newsh = s_realloc(sh, oldhdrlen+len+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+oldhdrlen;
    } else {
      //类型变化,申请新内存,复制数据,释放旧数据内存
        newsh = s_malloc(hdrlen+len+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, len);
    return s;
}

 

 
 
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值