redis源码阅读——sds

redis源码阅读——sds

简单动态字符串(simple dynamic strings),是redis中的基本数据结构,主要存储字符串和整数,本次阅读参考redis 5 设计与源码分析,阅读源码版本为redis 6 ,如有错误望指正。

数据结构

// 表示范围2^5 64,flags中前三位表示动态字符串类型,后五位表示字符串长度
// buf为柔性数组
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
// len表示以使用长度,alloc表示总长度,使用一个字节即2^8存储可表示范围256
// 在sdshdr8、 sdshdr16、sdshdr32、sdshdr64中flags前三位表示类型,后五位预留
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[];
};

首部长度计算

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

长度分配

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)
        return SDS_TYPE_5;
    if (string_size < 1<<8)
        return SDS_TYPE_8;
    if (string_size < 1<<16)
        return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
    if (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
#else
    return SDS_TYPE_32;
#endif
}

在sds中,字符串类型分为五种sdshdr5、sdshdr8、 sdshdr16、sdshdr32、sdshdr64分别表示不同的字符串长度,根据字符串长度进行动态分配,以及字符串扩容。在首部加上__attribute__ ((packed))取消字节对齐,使连接更加紧凑,节约内存。

表示范围

  • sds5 对应1<<5 32
  • sds8 对应 1<<8 256
  • sds16 对应 1<<16 65536
  • sds32 对应 1<<32 2^32
  • sds64 对应 1<<16 2^64

sds的基本增删改查操作

创建

// #define c  0
// #define SDS_TYPE_8  1
// #define SDS_TYPE_16 2
// #define SDS_TYPE_32 3
// #define SDS_TYPE_64 4
// #define SDS_TYPE_BITS 3
// #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;sds
    // 计算应该为字符串分配什么类型的sds
    char type = sdsReqType(initlen); 
    // 如果当sds长度为SDS_TYPE_5并且该buf长度为0,设置sds为SDS_TYPE_8也是为了防止后面会扩容
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    //计算各种不同类型头部各自所需长度
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */

   	// 分配长度+1则是因为添加字符串结尾"/0"
    sh = s_malloc(hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    // const char *SDS_NOINIT = "SDS_NOINIT";
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        // 不是 init 则清空 sh 的内存
        memset(sh, 0, hdrlen+initlen+1);
    // s 指向了 buf 开始的地址
    // 从上面结构可以看出,内存地址的顺序: len, alloc, flag, buf
    // 因为 buf 本身不占用空间,hdrlen 实际上就是结构的头(len、alloc、flags)
    s = (char*)sh+hdrlen;
    // flags占用一个字节,退一个字节就是flags开始位置
    fp = ((unsigned char*)s)-1;
    switch(type) {
        case SDS_TYPE_5: {
            // #define SDS_TYPE_BITS 3,当为SDS_TYPE_5时获取前三位确定类型
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            // #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
            // struct sdshdr8 *sh = (void*)((s)-(sizeof(struct sdshdr8))); 就是让sh找到结构体的地址
            // 赋值操作,后面依次类推
            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;
        }
    }
    // 如果 init 非空,则把 init 字符串赋值给 s,实际上也是 buf 的初始化
    if (initlen && init)
        memcpy(s, init, initlen);
    // 设置结束符
    s[initlen] = '\0';
    return s;
}

释放

redis在释放空间有两种操作,一种是通过定位到sds结构体首部,然后调用s_free释放内存,另一种则是将sds的len归零,实际上并没有清除数据,可以覆写

void sdsfree(sds s) {
    if (s == NULL) return;
    s_free((char*)s-sdsHdrSize(s[-1]));
}


void sdsclear(sds s) {
    sdssetlen(s, 0);
    s[0] = '\0';
}

附上len归零代码

static inline void sdssetlen(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                // 截取后五位设置长度为0
                unsigned char *fp = ((unsigned char*)s)-1;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            // 将结构体len设置为0
            SDS_HDR(8,s)->len = newlen;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->len = newlen;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->len = newlen;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->len = newlen;
            break;
    }
}

拼接

sds sdscatsds(sds s, const sds t) {
    return sdscatlen(s, t, sdslen(t));
}

sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);
	// 先判断sds是否需要扩容,需要则进行扩容,不需要返回原有sds
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    // 直接拼接
    memcpy(s+curlen, t, len);
    // 设置sds长度
    sdssetlen(s, curlen+len);
    // 添加结束符
    s[curlen+len] = '\0';
    return s;
}

扩容机制

/* 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;
    int hdrlen;

    /* Return ASAP if there is enough space left. */
    // 当可用空间大于拼接内容长度时,直接返回
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    // #define SDS_MAX_PREALLOC (1024*1024)
    // 当可用空间小于拼接内容长度时,分两种情况,当原有长度加上拼接长度小于1MB,分配两倍空间
    // 当小于时,原有长度加上拼接长度再加上1MB
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;

    // 判断扩容后sds类型
    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. */
    // 如果为SDS_TYPE_5设置类型为SDS_TYPE_8
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    // 获取头部长度
    hdrlen = sdsHdrSize(type);
    // 在这里分为两种情况,如果扩容后类型不变,改变分配空间大小最后加上类型头部空间
    // 否则重新开辟空间,然后进行相关属性统计
    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_free(sh);
        // 属性值重新设置
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    // 重新设置sds总长度大小
    sdssetalloc(s, newlen);
    return s;
}

扩容借鉴redis 源码设计与源码分析中一图

流程分析

  1. 判断可用空间是否大于拼接内容长度时,如果小于直接返回,否则进行扩容
  2. 这里分为两种情况,当可用空间小于拼接内容长度时,分两种情况,当原有长度加上拼接长度小于1MB,分配两倍空间,当小于时,原有长度加上拼接长度再加上1MB
  3. 然后计算得出新的sds的类型为,如果类型发生变化,重新分配内存,移动原有buf到新的空间,释放原有空间,最后进行相关统计数值赋值,如果类型没有发生变化,则使用realloc重新调整空间大小,然后进行相关统计数值赋值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值