String类型的转换顺序
- 当保存的值为整数且值的大小不超过long的范围,使用整数存储
- 当字符串长度不超过44字节时,使用embstr编码
(只实现一次分配内存空间,只允许读,若修改数据,就会转成raw编码) - 大于44字符时,用raw编码
sds
embstr和raw都为sds编码.
Redis是用C语言开发的,为什么不用C语言里面的字符串,而使用sds结构体呢?
- 低复杂度获取字符串长度; 有len存在,可以直接查出字符串长度,复杂度O(1);如果用C语言字符串,需要遍历整个字符串,复杂度为O(n)
- 避免缓冲区溢出; 进行两个字符串拼接C语言可使用strcat函数,但没有足够的内存空间.就会造成缓冲区溢出.但用sds合并时会用len检查内存空间是否满足需求,不满足再进行空间扩展,不会造成缓冲区溢出.
- 减少修改字符串的内存重新分配次数; C语言字符串不记录字符串长度,如果要修改字符串,要重新分配内存,不分配可能会造成内存缓冲区泄露.
Redis的sds实现了空间预分配和惰性空间释放两种策略
- 空间预分配
- 如果sds修改后,len长度<1mb.则会分配与len相同的未使用空间. 例 修改后字符串长度为100字节,那么会分配100字节的未使用空间,最终长度为 100+100+1(空字符\0)
- 如果长度≥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字节
网友评论