携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情 >>
redis数据结构——SDS
动态字符串SDS
我们都知道Redis中保存的Key是字符串,而value往往是字符串或者字符串的集合。所以字符串是Redis中最常用的一种数据结构。
但是redis在实现字符串的时候并没有采用c语言原生的字符串,最主要是因为c语言中的字符串存在以下问题:
c语言原生字符串存在的问题:
1. 获取字符串长度需要运算
// c语言,声明字符串
// 实际上c语言并没有字符串,所以下面定义的本质是字符数组: {'h', 'e', 'l', 'l', 'o', '\0'}
char* s = "hello"
而数组是连续储存的,所以c语言中在获取某个字符串的长度的时候,实际上就是对数组进行了遍历统计('\0'代表结束符)
2. 非二进制安全
就像上面所说的,c语言中的字符串默认是以'\0'为结束符的,所以在字符串的前面是不允许出现'\0'的,可是万一出现了呢?
万一出现了,则后面的字符将被舍弃。
3. 不可修改 c语言中所说的字符串本质上是字符数组,在定义之后是属于定长的,所以后面对于增删方面很是不便利。
redis中自己实现的字符串结构——简单动态字符串(SDS)
比如,我们在redis中执行以下命令,redis将在底层创建两个SDS,其中一个是包含“name”的SDS,另一个是包含“21”的SDS。
127.0.0.1:6379> set name dalao
OK
那SDS究竟是什么呢?它如何实现的呢?
下面这个就是SDS的结构体定义:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* buf中已保存的字符串字节数,不包含结束标示*/
uint8_t alloc; /* buf申请的总的字节数,不包含结束标示*/
unsigned char flags; /* 不同SDS的头类型,用来控制SDS的头大小*/
char buf[];
};
经过以上代码,我们可以发现,SDS本质还是一个“字符串”,不过它在结构体中,多了一些数据的记录,比如,有len的记录,SDS就不再需要像c语言那样通过遍历统计长度了,同时也解决了二进制安全(SDS不承认结束标志)。
举个栗子,大家就能秒懂SDS的结构了:
SDS之所以称为动态字符串,是因为它已经实现了动态扩容!
SDS动态扩容机制,申请空间:
如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1;
如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。称为内存预分配。
ps:上述的两种情况中末尾的 + 1 是为了存放'\0'的
所以,假如我们都忽略掉'\0',只考虑字符串自己的实际空间的话,扩容机制如下:
如果新字符串小于1M,则新空间(alloc)为扩展后字符串长度的两倍;
如果新字符串大于1M,则新空间(alloc)为扩展后字符串长度+1M。称为内存预分配。
举个栗子,比如原有字符串“Hi”,
现在要变成“Hi,Amy”:
字符串有“Hi”变成“Hi,Amy”,长度是由2 变 6,故,len 是由2 变 6 因为alloc 和 len都不记录'\0'的,所以alloc是由2 变 12.