详解redis数据结构——SDS

146 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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的结构了:

image.png

SDS之所以称为动态字符串,是因为它已经实现了动态扩容!

SDS动态扩容机制,申请空间:

如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1;

如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。称为内存预分配。

ps:上述的两种情况中末尾的 + 1 是为了存放'\0'的

所以,假如我们都忽略掉'\0',只考虑字符串自己的实际空间的话,扩容机制如下:

如果新字符串小于1M,则新空间(alloc)为扩展后字符串长度的两倍;

如果新字符串大于1M,则新空间(alloc)为扩展后字符串长度+1M。称为内存预分配。

举个栗子,比如原有字符串“Hi”,

image.png

现在要变成“Hi,Amy”:

image.png

字符串有“Hi”变成“Hi,Amy”,长度是由2 变 6,故,len 是由2 变 6 因为alloc 和 len都不记录'\0'的,所以alloc是由2 变 12.