REDIS 数据类型之 SDS

2021/07/20

REDIS 数据类型之 SDS

SDS分配一段连续的内存来存储动态字符串

1.SDS的数据结构

  • len: 记录buf数组中已使用字节的数量 , 等于 SDS 保存字符串的长度.
  • alloc: 分配的字符数量(可用长度)
  • flags: 类型标志位
  • buf[]: 存储的字符串内容
typedef char *sds;

//SDS的类型
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

//适配不同长度
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; //字符串真实长度,不包括终止符
    uint8_t alloc;  //字符串最大容量,不包括终止符
    unsigned char flags; //head的类型
    char buf[]; //字符串主体
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len;
    uint16_t alloc; 
    unsigned char flags;
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; 
    uint32_t alloc;
    unsigned char flags; 
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; 
    uint64_t alloc;
    unsigned char flags;
    char buf[];
};

2.SDS的优点

  1. 获取字符串长度O(1) , 数据结构中len中存储了SDS 保存字符串的长度.
  2. 防止缓冲区溢出, C 字符串不记录自身的长度 ,所以字符串复制的时候,可能会产生缓冲区溢出.
  3. 优化动态字符串的内存分配
    • 空间预分配: 对SDS修改时,会分配len长度(最大1M)的空间,当SDS连续增长时,空间分配次数会减少.
    • 惰性空间释放: SDS进行缩短操作时,不会重新分配内存,而是使用free属性存储未使用的字节数
  4. 二进制安全,如果保存二进制数据,遇到 ‘\0’ ,会认为字符结束,导致数据丢失.SDS可以根据len来判断是否结束.

3.SDS的创建

init属性: 要存储的字符串

initlen属性: 存储字符串的长度

sdsReqType: 根据长度 获取初始 type,type为SDS_TYPE_5/8/16/32/64,用于适配不同的长度

sdsHdrSize: 计算SDS的Header部分的长度.

s_malloc: 申请分配内存空间,申请的空间为header长度+主体长度 + 1(结束符 ‘\0’)

memset: 初始化内存, 将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换

memcpy: 内存复制, 从src的开始位置拷贝n个字节的数据到dest.

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    // 根据长度 获取初始 type
    char type = sdsReqType(initlen);
  	// 空的字符串通常被创建成 type 8,因为 type 5 已经不实用了
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    // 获取 header 长度
    int hdrlen = sdsHdrSize(type);
    
    unsigned char *fp;
	// 创建内存空间,空间大小等于 header长度+主体长度 + 1,后面加1是因为需要追加结束符,兼容 C 字符串
    sh = s_malloc(hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
         // 初始化
        memset(sh, 0, hdrlen+initlen+1);
     // 主体内容 指针地址,相当于整个 SDS 结构体向后偏移了整个 header 长度
    s = (char*)sh+hdrlen;
    // flags 指针为 主体 SDS 内容向前偏移1位(这个上面我们已经解释过了)
    fp = ((unsigned char*)s)-1;
    // 根据 type 值进行 header 各个字段的初始化
    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);
    // 兼容 C 函数,在 字符串后添加结束符
    s[initlen] = '\0';
    return s;
}

总结:

  1. 根据存储长度获取type(5/8/16/32/64)
  2. 创建内存空间hdrlen+initlen+1(header长度+主体长度 +’\0’)
  3. 初始化内存空间,默认都是’0’
  4. 设置header的len/alloc
  5. 通过memcpy设置字符串值
  6. 设置最后一位为C语音字符串结束符

4.SDS惰性删除

sds的删除并不是直接回收内存,而是修改字符,让其为空字符,这其实是惰性释放 .

下次使用时就可以不用再申请空间.

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

static inline void sdssetlen(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                unsigned char *fp = ((unsigned char*)s)-1;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            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;
    }
}

5.SDS的扩容

sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}

sds sdscatlen(sds s, const void *t, size_t len) {
    //获取sds当前的长度
    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;
}

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

sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    //剩余可用长度
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

   	//剩余长度还够用,不需要扩容
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    //sds需要长度
    newlen = (len+addlen);
    assert(newlen > len);   
    if (greedy == 1) {
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    type = sdsReqType(newlen);
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > len); 
    //类型扩容后是否发生了变化
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        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;
    //1M,大于1M扩容1M,小于扩容双倍
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;
}

//计算剩余可用长度(alloc-len)
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;
}

能摸鱼就很舒服

Show Disqus Comments
扫码关注公众号:纯洁的微笑
发送 290992
即可立即永久解锁本站全部文章