Redis中的动态字符串

前言

redis中的SDS(dynamic strings library)是在Redis中的一种基本的数据结构,这里将对sds进行解析。

子类型

typedef char *sds;
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; //实际使用的长度
    uint8_t alloc; //长度,可以理解为容量
    unsigned char flags; //低三位表示类型
    char buf[];
};

sdshdr16、sdshdr32、sdshdr64和sdshdr8都类似,这里不多介绍,sdshdr5也根本没使用,这里重点介绍sdshdr8,首先在结构体关键字struct后面有__attribute__ ((packed)),这个是禁止内存对齐,节约空间,在结构体成员后面有一个char buf[],这里是柔性数组,sdshdr8的大小仅为3,buf[]并不占用内存。紧接着,有以下宏定义:

#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
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3

类型从0到4,flags中的低三位完全可以表示,下面就是获取类型的方法:

struct sdshdr8 s8;
s8.flags & 0x07 // s8.flags & SDS_TYPE_MASK

但可能会有一个疑问,我明知道它的类型,我再获取它的类型不是多此一举吗,这个问题先放这里,待会儿再说。

子类型的应用

定义了这么多子类型,肯定是为不同长度的字符串选择最合适的类型,不然就是浪费空间,因此就来了sdsnew函数:

sds sdsnew(const char *init) { // sds就是char *
    size_t initlen = (init == NULL) ? 0 : strlen(init);//计算出来字符串的长度
    return sdsnewlen(init, initlen);
}

在sdsnewlen函数中:

sds sdsnewlen(const void *init, size_t initlen) {
    return _sdsnewlen(init, initlen, 0);
}

_sdsnewlen主要实现在这里:

sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
    void *sh;//定义泛型指针
    sds s;//定一个字符串指针
    char type = sdsReqType(initlen);//根据字符串的初始长度选择一个最合适的类型,就是我们上面提到的SDS_TYPE_16这些,这里实现比较简单,不多叙述
    //最低也是使用SDS_TYPE_8,放弃 SDS_TYPE_5
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);//根据计算的类型返回各个子类型结构体的大小
    unsigned char *fp; /* flags pointer. */
    size_t usable;
	//和内存分配的时候一样,判断是否溢出
    assert(initlen + hdrlen + 1 > initlen); 
    //根据trymalloc的值选择分配内存的函数,重新分配内存大小是:初始长度+最合适的子类型结构体大小+1,这个1是为‘\0’准备
    sh = trymalloc?
        s_trymalloc_usable(hdrlen+initlen+1, &usable) :
        s_malloc_usable(hdrlen+initlen+1, &usable);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);//初始化,全部赋0
    //这步骤把sh向前移动hdrlen个字节,直接指向的是字符串的内容
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;//这是指向flags的指针
    usable = usable-hdrlen-1;//可用的内存大小
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    //内存已经分配好加初始化,现在开始把相关信息写入
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            //又是一个宏
            //执行之后的效果是struct sdshdr8 *sh = (void*)((s)-(sizeof(struct sdshdr8)));
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}

注意:

  • 此时返回的s依然是char *,也就是sds,但是s指针之前的hdrlen长度已经有信息写入
  • 分配内存的时候会根据字符串的长度自动分配
  • 虽然结构体内有记录字符串长度的信息,但是字符串仍然是以’\0’结束

sdsfree

有分配就有释放,释放的函数sdsfree为:

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

s[-1]是sds中的flag,sdsHdrSize(s[-1])获取的是结构体的大小,(char*)s-sdsHdrSize(s[-1])才是实际申请的内存首地址,s_free就是z_free

其他的函数

sdsupdatelen

void sdsupdatelen(sds s) {
    size_t reallen = strlen(s);
    sdssetlen(s, reallen);
}

这个函数时为了让我们直接对s进行操作时实时更新其实际长度。

sdsMakeRoomFor

如果我们想对一个字符串进行扩展,就需要开辟更大的空间:

sds sdsMakeRoomFor(sds s, size_t addlen) {
    return _sdsMakeRoomFor(s, addlen, 1);
}

具体实现的函数是_sdsMakeRoomFor

sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);//看看还有多少空间可用
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;//剩余空间够用

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */
    if (greedy == 1) {//为1的时候表示自动增添空间
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    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;

    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);//这个其实就是z_realloc_usable
        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_usable(hdrlen+newlen+1, &usable);
        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);
    }
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;
}

sdsResize

这里的resize调整的是alloc,也就是容量

sds sdsResize(sds s, size_t size) {
    void *sh, *newsh;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
    size_t len = sdslen(s);
    sh = (char*)s-oldhdrlen;

    /* Return ASAP if the size is already good. */
    if (sdsalloc(s) == size) return s;

    /* Truncate len if needed. */
    if (size < len) len = size;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
    type = sdsReqType(size);
    /* Don't use type 5, it is not good for strings that are resized. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    hdrlen = sdsHdrSize(type);

    /* If the type is the same, or can hold the size in it with low overhead
     * (larger than SDS_TYPE_8), we just realloc(), letting the allocator
     * to do the copy only if really needed. Otherwise if the change is
     * huge, we manually reallocate the string to use the different header
     * type. */
    if (oldtype==type || (type < oldtype && type > SDS_TYPE_8)) {
        newsh = s_realloc(sh, oldhdrlen+size+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+oldhdrlen;
    } else {
        newsh = s_malloc(hdrlen+size+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
    }
    s[len] = 0;
    sdssetlen(s, len);
    sdssetalloc(s, size);
    return s;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值