redis源码解读--动态字符串SDSHDR
阅读源码: sds.h sds.c
SDSHDR 全称 Simple Dynamic Strings Header
sds
char *的别名
|
|
sdshdr
sdshdr有好几个类别,它们分别是:sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64,其中sdshdr5是不使用的
源码如下:
|
|
这五个结构体中,len表示字符串的长度,alloc表示buf指针分配空间的大小,flags表示该字符串的类型(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64),是由flags的第三位表示的,至于为何怎么说,请看下方的源码:
|
|
可以看出SDS_TYPE只占用了0,1,2,3,4五个数字,正好占用三位,我们就可以使用flags&SDS_TYPE_MASK来获取动态字符串对应的字符串类型
注意一个小细节:attribute ((packed)),这一段代码的作用是取消编译阶段的内存优化对齐功能。
例如:struct aa {char a; int b;}; sizeof(aa) == 8;
但是struct attribute ((packed)) aa {char a; int b;}; sizeof(aa) == 5;
这个很重要,redis源码中不是直接对sdshdr某一个类型操作,往往参数都是sds,而sds就是结构体中的buf,在后面的源码分析中,你可能会经常看见s[-1]这种魔法一般的操作,而按照sdshdr内存分布s[-1]就是sdshdr中flags变量,由此可以获取到该sds指向的字符串的类型。
SDS中的宏定义函数
SDS_HDR_VAR(T,s)
|
|
查看这段代码首先得明白 C 语言中的宏定义##操作符,我在此就不再解释了
这段代码就很骚气了,用个例子来解释:
|
|
这样就可以根据指向buf的sds变量s得到sdshdr8的指针,是不是感觉很神奇?
SDS_HDR(T,s)
|
|
同上方的函数类似,根据指向buf的sds变量s得到sdshdr的指针,只不过这里是获取的是指针地址,上方函数是创建了一个变量
SDS_TYPE_5_LEN(f)
|
|
看名字就知道该函数就是获取sdshdr5字符串类型的长度,由于根本不使用sdshdr5类型,所以需要直接返回空,而flags成员使用最低三位有效位来表示类型,所以让f代表的flags的值右移三位即可
上方基本上就是sds的核心内容了,然后再看看sds中的几个内联函数
static inline size_t sdslen(const sds s)
|
|
该处就使用到了取消编译阶段的内存优化对齐功能,直接使用s[-1]获取到flags成员的值,然后根据flags&&SDS_TYPE_MASK来获取到动态字符串对应的类型进而获取动态字符串的长度。
static inline size_t sdsavail(const sds s)
|
|
获取动态字符串可使用的空间,从这里可以看出来,SDS和我平常所用到的C语言的原生字符串有差别,因为从获取可用空间的计算方法来看,并未考虑到字符串需要以\0结尾,因为结构体本身带有长度的成员len,不需要\0来做字符串结尾的判定,而且不使用\0作为结尾有很多好处,可以存储的类型多样性就提高了。
static inline void sdssetlen(sds s, size_t newlen)
|
|
设置sds的长度
static inline void sdsinclen(sds s, size_t inc)
|
|
增加sds的长度
static inline size_t sdsalloc(const sds s)
|
|
获取sds已分配空间的大小
static inline void sdssetalloc(sds s, size_t newlen)
|
|
设置sds已分配空间的大小
SDS函数
|
|
这当中的大部分函数都很简单,只是对zmalloc文件里面的函数,sds中inline函数,或者是sdsnewlen函数的一层简单调用,就不解释,我们挑几个重点的看看。
sds sdsnewlen(const void *init, size_t initlen)
|
|
该函数是一个生成新sds的函数,根据init指针和initlen参数来初始化sds的内容,根据init是否是SDS_NOINIT来设置是否需要使用init的内容初始化sds,如果是SDS_NOINIT,那么默认会将sds置为一串为未知的字符串,如果init为NULL。那么默认会将sds置为一个空字符串,并且sdsnewlen在为sds向系统申请一个新的空间的时候,新空间的长度是hdrlen+initlen+1,注意这里的+1,这多出来的一个字节,其实是放\0的,这样sds就可以使用C自带标准库(strlen,strtoll之类的函数)来操作sds中buf的内容,不然还得自己写一套,很是麻烦。
以下是使用sdsnewlen函数简单调用构成的函数:
|
|
以下是对zmalloc里面的函数简单调用或者直接是别名的函数:
|
|
在动态字符串的所有操作中,大部分会进行对内存的扩大和释放,所以得介绍一下sds中对内存扩大和释放的函数
扩大sds的空闲空间 – sdsMakeRoomFor函数
|
|
该函数便是扩大sds空间,但是感觉上还是想让sds中available空间的大小能够容纳addlen大小的字符串,并不是改变了sds中buf的长度,而是改变了sds中available空间的大小,如果当前available空间的大小大于addlen的大小,那么便不作修改,如果available空间的大小小鱼addlen的大小,那么就会重新分配sds中alloc的大小,newlen并不是无脑直接让alloc加上addlen,而且使用sds的长度加上addlen的长度作为newlen,但是经常重新分配内存会对效率有所影响,但是为了防止重新分配内存对效率的影响而让newlen无脑翻倍的话,又会对内存造成影响,造成内存占用过高,但是很大一部分内存并没有使用,所以取得了一个折中的办法,就是在newlen小于SDS_MAX_PREALLOC(1M),对newlen进行翻倍,在newlen大于SDS_MAX_PREALLOC的情况下,让newlen加上SDS_MAX_PREALLOC。
sdsRemoveFreeSpace
|
|
就是对sds中多余的空间进行释放,例如以前是一个sdshdr64的sds,在redis运行过程中,buf的内容被修改了,变短了,那么多出来的内容就需要释放掉,还给系统,并且,如果修改得比较多,现在一个sdshdr16的sds就能容纳下,那么当前sds的type还会被修改,因为不同的sds类型占用的空间也是不一样的,并且杀鸡焉用宰牛刀,是吧。
其他的函数介绍意义其实并不大,都很简单,我们仅需要知道sds的内存分布,内存操作即可。
- 原文作者:Daryl
- 原文链接:https://siskinc.github.io/post/redis%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB-%E5%8A%A8%E6%80%81%E5%AD%97%E7%AC%A6%E4%B8%B2sdshdr/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。