搞懂Redis(二)-1:String数据结构

136 阅读3分钟

String类型的转换顺序

  • 当保存的值为整数且值的大小不超过long的范围,使用整数存储
  • 当字符串长度不超过44字节时,使用embstr编码 (只实现一次分配内存空间,只允许读,若修改数据,就会转成raw编码)
  • 大于44字符时,用raw编码

sds

embstr和raw都为sds编码.

Redis是用C语言开发的,为什么不用C语言里面的字符串,而使用sds结构体呢?

  1. 低复杂度获取字符串长度; 有len存在,可以直接查出字符串长度,复杂度O(1);如果用C语言字符串,需要遍历整个字符串,复杂度为O(n)
  2. 避免缓冲区溢出; 进行两个字符串拼接C语言可使用strcat函数,但没有足够的内存空间.就会造成缓冲区溢出.但用sds合并时会用len检查内存空间是否满足需求,不满足再进行空间扩展,不会造成缓冲区溢出.
  3. 减少修改字符串的内存重新分配次数; C语言字符串不记录字符串长度,如果要修改字符串,要重新分配内存,不分配可能会造成内存缓冲区泄露.

Redis的sds实现了空间预分配和惰性空间释放两种策略

  • 空间预分配
  1. 如果sds修改后,len长度<1mb.则会分配与len相同的未使用空间. 例 修改后字符串长度为100字节,那么会分配100字节的未使用空间,最终长度为 100+100+1(空字符\0)
  2. 如果长度≥1mb,每次分配1mb未使用空间
  • 惰性空间释放 对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用free属性将这些字节数量记录下来,等待后续使用(也可手动触发字符串缩短)

二进制安全: C语言字符串是用空字符来判断结束,但对于一些二进制文件(如图片等),其内容包含空字符串,因此C语言字符串无法正确存取.而所有的sds的api都是以二进制的方式来处理buf里面的元素,并且sds是以len长度来判断字符串是否结束.

遵从空字符结束的惯例,这样可以重用C语音库<string.h>的一部分函数

为什么小于44字节的用embstr呢?

再看一下rejectObject和sds定义的结构(短字符串的embstr用最小的sdshdr8)


typedef struct redisObject {
    // 类型 4bits
    unsigned type:4;
    // 编码方式 4bits
    unsigned encoding:4;
    // LRU 时间(相对于 server.lruclock) 24bits
    unsigned lru:22;
    // 引用计数 Redis里面的数据可以通过引用计数进行共享 32bits
    int refcount;
    // 指向对象的值 64-bit
    void *ptr;
} robj;

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

RedisObject的占用空间 4+4+24+32+64 = 128bits = 16字节 sdsdr8占用空间 1(uint8_t) + 1(uint8_t)+ 1 (unsigned char)+ 1(buf[]中结尾的'\0'字符)= 4字节

初始最小分配为64字节,所以只分配一次空间的embstr最大为 64 - 16- 4 = 44字节