Redis基础知识----SDS(简单动态字符串)

以下的总结,基于redis源码4.0.9版本

1、redis的底层存储数据结构
简单动态字符串(SDS)、链表、字典、跳跃表、整树集合、压缩列表等
2、redis的数据结构对象
字符串、列表、哈希、集合、有序集合等

  1. 简单动态字符串(sds)

SDS通过字符串的长短不同实现了5中不同的数据结构,其不同在于头部用来标识字符串长度的类型,主要用来减小结构体的大小,节省内存。
例如:长度小于32的字符串,则会使用sdshdr5结构体,sdshdr5结构体特殊在并没有lenalloc属性,其巧妙的使用了flags的高5位用来标识字符串的长度。

 sdshdr5   ---->    <32 
 sdshdr8   ---->    <256 
 sdshdr16  ---->    < 2^16
 sdshdr32  ---->    < 2^32
 sdshdr64  ---->    < 2^64

基本数据结构如下:

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

len代表字符串的真实长度

alloc排除header和terminator外的分配长度

flags类型标识了结构体的实际类型

buf[]真实数据

sdshdr5数据结构

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};

字符串的扩容

/*
 * 在必要情况下对SDS进行扩容
 *
 * 参数列表
 *      1. s: 待扩容对SDS对字符串指针
 *      2. addlen: 需要新加入字符串的长度
 *
 * 返回值
 *      返回扩容后新的sds,如果没扩容则和入参sds地址相同
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    // 首先计算出原SDS还剩多少可分配空间
    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);
    // 用sds(指向结构体尾部,字符串首部)减去结构体长度得到结构体首部指针
    // 结构体类型是不确定的,所以是void *sh
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    // 如果新长度小于最大预分配长度则分配扩容为2倍
    // 如果新长度大于最大预分配长度则仅追加SDS_MAX_PREALLOC长度
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    // 字符串的长度更改了,使用对头部类型可能也会变化
    type = sdsReqType(newlen);
    // 由于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 */
        // 头部类型有变化则重新开辟一块内存并将原先整个SDS拷贝一份过去
        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;
}

关键知识点:
1.SDS记录长度,O(1)获取整个长度
2.减少字符串修改时内存分配次数。内存预分配,分配一定长度的字符串,扩容时如果扩容需要的len小于SDS_MAX_PREALLOC,则扩大len的2倍,否则扩大SDS_MAX_PREALLOC(1M)(#define SDS_MAX_PREALLOC (1024*1024))。通过len和alloc实现惰性释放,字符串删除后,长度不变,便于下次使用。
3.二进制安全。不是靠空字符来判断字符串的结束的,而是通过len这个属性。
4.杜绝缓存去溢出。字符串合并前,会检查要当前字符串的长度,不足,扩容。规则如 2.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值