Redis数据结构-sds

介绍

在redis数据结构中,字符串类型没有直接使用c语言中的字符串,而是使用了sds(simple dynamic string,简单动态字符串)。sds在redis中运用十分广泛,例如,key-value中的key就是以sds为基础。我们先来看一下sds的定义,再来说明使用sds的原因。

sds的定义

在介绍sds之前,我们来简单回顾下c语言中字符串的结构,例如,下图为字符串”Redis“在内存中的存储方式
在这里插入图片描述
有以下两个特点:

  • 除了分配必要的内存空间外,还要额外分配一个字节用来存储’\n’,因为在c语言以’\n’作为字符串的终止符
  • 字符串没有维护自身的长度信息,必须要遍历整个字符串才能知道它的长度

再来看sds的定义,每个sds都包含了len、alloc、flags和buf这4个部分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lH3ijz2i-1571483202732)(/Users/admin/Library/Application Support/typora-user-images/image-20191019181123403.png)]

  • len:sds中c字的长度(不包括’\n’)
  • alloc:已分配的空间(包括header和c字符串末尾的’\n’)
  • flags:低3位记录sds类型,高5位未使用
  • buf:存储c字符串

sds类型

上面介绍flags时提到”sds类型“,在redis中sds总共有5中类型,分别是

  • SDS_TYPE_5

  • SDS_TYPE_8

  • SDS_TYPE_16

  • SDS_TYPE_32

  • SDS_TYPE_64

不同类型在redis中的定义

#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

对于不同类型的sds,redis为其分配的内存空间大小也不相同,其目的是为了节省内存。以下是不同类型的sds header的定义

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; /* 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类型

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 (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
}

相关函数

  • 创建一个指定长度的sds并保存对应的值
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);//根据长度决定sds的类型
    // 空字符串一般用作追加来使用,这里优化了下,使用SDS_TYPE_8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp;//指向flags的指针

    sh = s_malloc(hdrlen+initlen+1);// 申请内存,header长度 + 初始长度 + 1(结束位\0)
    if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    if (sh == NULL) return NULL;//申请内存失败,返回NULL
    s = (char*)sh+hdrlen;//c字符串开始位置位置的指针
    fp = ((unsigned char*)s)-1;//因为flags长度固定为1个字节,s向左偏移1位,就能得到flag的指针
    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);//如果init中有内容,则将其保存到新创建的sds中
    s[initlen] = '\0';
    return s;//返回sds
}
  • 更新sds长度信息
void sdsupdatelen(sds s) {
    int reallen = strlen(s);
    sdssetlen(s, reallen);
}

这个函数是为了防止在某些情况下操作sds后,sds的len信息不正确的情况。例如,我手动修改了sds中c字符串的内容,修改后c字符串的长度应该为2,而不再是之前的6,因此需要调用sdsupdatelen函数更正len信息。

 s = sdsnew("foobar");
 s[2] = '\0';
 sdsupdatelen(s);
 printf("%d\n", sdslen(s));
  • sds空间扩充
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;  // 一般会在字符串追加操作的时候调用这个函数,保证sds有足够的空间来存储修改后的字符串,如果剩余空间可以满足的话,直接返回即可,无需扩容操作

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)  // 如果目标长度小于1M,则将sds的容量扩充为之前的2倍
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC; // 如果目标长度小于大于1M,则在原来的基础上扩容1M的空间

    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);
    if (oldtype==type) {
      	//前面提到过,对于不同长度的字符串,redis使用的不同的的sds类型及不同大小的header,如果扩容后类型不变的情况下,只需要调用s_realloc函数对内存空间进行调整
        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 */
      	// 如果sds类型改变的情况下,需要调用s_malloc函数重新分配内存并移动字符串
        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;
}

// 计算sds当前剩余空间
static inline size_t sdsavail(const sds s) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5: {
            return 0;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            return sh->alloc - sh->len;//已分配的空间-已使用的空间
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            return sh->alloc - sh->len;
        }
    }
    return 0;
}
  • sds缩容

既然有sds扩容的函数,也会有对应的缩容函数,否则字符串占用的空间只会越来越多,内存空间不能及时释放。

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

    type = sdsReqType(len);
    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {// 如果缩容后的type和之前一样,则调用s_realloc函数重新分配空间即可
        newsh = s_realloc(sh, hdrlen+len+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {    // 如果缩容后的type和之前不一样,还需要改变header类型
        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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值