redis简单动态字符串SDS

redis简单动态字符串SDS_第1张图片

目录

前言

介绍

优点

内存分配原则和惰性释放


前言

从今天开始我们就要学习redis的源码了,想想还有点小激动呢。

前方高能预警,非战斗人员迅速撤离。

但是咱怎么能怂呢,撤离啥啊,说干就干,死磕源码,这代码也是人写的,他还能整出什么幺蛾子。

又一个但是来了,redis底层是用C语言写的,如果对C语言一窍不通,那还是算了,前方等待的是一座大山。墙裂推荐去了解一下C.emmmm,幸好我会C,哈哈哈,毕竟他是开启偶代码之路的小哥哥。

好啦,不扯淡了,说说正经事。

redis简单动态字符串SDS_第2张图片

 

介绍

Redis没有直接使用C语言传统的字符串来表示(以空字符串结尾的字符数组),而是自己构造了一种名为简单动态字符串SDS。

之前看的String类型的数据结构底层就是用SDS实现的。

SDSHDR(SDS的表头结构如下):

struct sdshdr {
 int len; //buf中已占用空间的长度 
 int free; //buf中剩余可用空间的长度
 char buf[]; //初始化sds分配的数据空间,而且是柔性数组(Flexible array member) 
};

举个栗子:

我们之前设置一个名为str1的字符串,值为redis,其实他在内存上的结构大致如下:

redis简单动态字符串SDS_第3张图片

len为5,表示这个sds长度为5个字节。

free为2,表示这个sds还有2个字节未使用的空间。

buf为char[]的数组,分配了(len+1+free)个字节的长度,前len个字节保存redis这5个字符串,接下来1个字节保存了'\0',剩下的free个字节未使用。

 

优点

1.二进制安全。

因为传统的C语言字符串符合ASCII编码,而他的特点是遇零则止,所以当读一个字符串的时候,只要遇到'\0',就认为到达了末尾。这个问题就来了,如果保存的是图片或视频等二进制文件,就会被强行截断,那么数据就不完整了。

那现在不能通过遇零则止来判断是否这个字符串读完了,但是现在可以通过len与buf[]数组的长度比较,如果len+1等于buf的长度,就说明这个字符串读完了。

2.获取字符串长度的操作,其时间复杂度为O(1)。

原来传统的C字符串获得长度的做法是遍历字符串的长度,如果遇零就返回,其时间复杂度为O(n)。

而SDS表头的len成员就保存了字符串的长度,其时间复杂度为O(1)。

3.杜绝缓存区溢出。

因为SDS表头的free成员记录着buf字符数据中未使用的数量,所以,在进行append命令的时候,先判断free是否够用,如果够用,就直接添加字符,如果不够用,就先进行内存扩展,再进行添加字符串。

 

内存分配原则和惰性释放

前面说的内存会扩展,但是呢,不是随便扩展的。就像奥特曼要小怪兽,一个闪光就行,但是要是终极大boss,就要好好打了,注意策略,很有可能会场外求救呢。

如果SDS表头len成员小于1MB(1024X1024),就分配和len长度相同的未使用空间。

如果SDS表头len成员大于等于1MB(1024X1024),就分配1MB的未使用空间。

空说无凭,翠花,上代码。

sds sdsMakeRoomFor(sds s, size_t addlen) { //对 sds 中 buf 的长度进行扩展 
 struct sdshdr *sh, *newsh;
 size_t free = sdsavail(s); //获得s的未使用空间长度
 size_t len, newlen; //free的长度够用不用扩展直接返回
 if (free >= addlen) return s; //free长度不够用,需要扩展 
 len = sdslen(s); //获得s字符串的长度
 sh = (void*) (s-(sizeof(struct sdshdr))); //获取表头地址
 newlen = (len+addlen); //扩展后的新长度 

 //空间预分配    
 //#define SDS_MAX_PREALLOC (1024*1024) 
 //预先分配内存的最大长度为 1MB 
 if (newlen < SDS_MAX_PREALLOC) //新长度小于“最大预分配长度”,就直接将扩展的新长度乘2 
   newlen *= 2; 
 else 
   newlen += SDS_MAX_PREALLOC; //新长度大于“最大预分配长度”,就在加上一个“最大预分配长度”  

 newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1); //获得新的扩展空间的地址
 if (newsh == NULL) 
    return NULL; 
 newsh->free = newlen - len; //更新新空间的未使用的空间free 
return newsh->buf;
}

这边说的是内存扩展,如果新的字符串比旧的字符串要短很多,那么他是不是要回收呢?

先看重置代码,很明显,程序并没有回收多出来的长度,而是使用free来将这些字符串记录起来,等到将来使用。

void sdsclear(sds s) { //重置sds的buf空间,懒惰释放 
 struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
 sh->free += sh->len; //表头free成员+已使用空间的长度len = 新的free 
 sh->len = 0; //已使用空间变为0 
 sh->buf[0] = '\0'; //字符串置空
}

此上就是SDS的源码,如果要看他具体每个操作的步骤,就要看具体的文件了sds.c和sds.h。

emmmm,偶反正是不行了。

redis简单动态字符串SDS_第4张图片

 

你可能感兴趣的:(redis)