简介
redis 源码版本:3.05.04
如有理解不对的地方,欢迎各位指出,大家共同交流和学习。
源码分析
为什么文件叫sds那?因为,sds是simple dynamic string的英文缩写,也就是简单的动态字符串。
sds的底层还是char *数据类型
typedef char *sds;
sds 结构:
struct sdshdr {
unsigned int len; //当前字符串已占空间
unsigned int free; //剩余可用空间
char buf[]; //字符数组,存放实际数据
sds对象示意图如下:
如上图所示,buf作为一个字符数组,与c/c++语言的字符串类似,都是以‘\0’来结束字符串。其中,len表示当前buf中的字符数,不包括后面的结束符’\0’,free表示当前buf中剩余的空间数,整个buf的大小就是len+free+1。
那么一个sdshdr对象占多大空间那?
即:
sizeof(sdshdr) = 8
因为,结构体中的char buf[]只作为一个符号地址存在,不占内存空间。且它还有一个名字叫柔性数组成员,必须是结构体的最后一个成员,柔性数组成员不仅可以用于字符数组,还可以是元素为其他类型的数组,如int i[];且结构体中只能出现一个柔性数组成员,否则,最后一个柔性数组成员前面的柔性数组成员就会报错,提示"不允许使用不完整的数据类型"。
补充一下,柔性数组和一个指针的区别。
首先,看一个Demo代码:
#include<iostream>
#include<string>
using namespace std;
struct sdshdr_1 {
unsigned int len;
unsigned int free;
char buf[]; //柔性数组
};
struct sdshdr_2 {
unsigned int len;
unsigned int free;
char *buf; //指针
};
int main()
{
cout << sizeof(sdshdr_1) << endl; //结果为:8
cout << sizeof(sdshdr_2) << endl; //结果为:12
system("pause");
return 0;
}
可以看出,指针会占用4字节(32位)的内存空间,而柔性数组不占用内存空间,所以,采用这种方式可以节省内存空间。在sdsnewlen核心函数中,就利用到了柔性数组来保存实际数据,根据initlen参数(数据长度)动态分配内存,有效利用内存空间。具体代码如下
创建一个指定长度的sds字符串
这个函数是后面操作sds字符串的核心。
sdsnewlen函数
//创建一个指定长度的sds字符串 这个也是sds字符串的核心函数,后面操作sds字符串的函数底层调用的就是这个函数
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
if (init)
{
//zmalloc 用来分配内存,不初始化内存(详细后面会讲到,这里暂且当成malloc理解即可)
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else
{
//zcalloc 也是用来分配内存,同时进行初始化(详细后面会讲到,这里暂且当成malloc和memset初始化操作理解即可)
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = (int)initlen; WIN_PORT_FIX /* cast (int) */
//因为是新的字符串,所以,不预留内存空间,即分配的内存就是字符串实际所占用的大小,不预留空间
sh->free = 0;
if (initlen && init)
//如果initlen和init初始化内容同时非空,则将初始化的init字符串复制到buf中
memcpy(sh->buf, init, initlen);
//最后以'\0'结尾
sh->buf[initlen] = '\0';
//返回对应字符串首地址,进行打印时,输出的就是字符串
return (char*)sh->buf;
}
为什么分配空间时加1?
sizeof(struct sdshdr)+initlen+1
因为,buf中的字符是以’\0’结尾,表示字符至此结束。
上面的核心函数结合如下Demo理解:
#include<iostream>
#include<string>
using namespace std;
struct sdshdr {
unsigned int len; //已占空间 sizeof()
unsigned int free;//剩余可用空间
char buf[]; //存放实际数据
};
typedef char *sds;
sds newlen(const void * init, size_t initlen)
{
sdshdr * sh;
if (init)
{
sh = (sdshdr *)malloc(sizeof(struct sdshdr) + initlen + 1);
}
else
{
sh = (sdshdr *)malloc(sizeof(struct sdshdr) + initlen + 1);
memset(sh, 0, sizeof(struct sdshdr) + initlen + 1);
}
if (sh == NULL) return NULL;
sh->len = (int)initlen;
sh->free = 0;
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
int main()
{
cout << newlen("abc", 3) << endl;
system("pause");
return 0;
}
结果:abc
sdsempty函数
创建一个空sds字符串
sds sdsempty(void) {
return sdsnewlen("",0);
}
sdsnew函数
创建一个sds字符串
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
sdsdup函数
复制一个sds字符串
sds sdsdup(const sds s) {
//init非空时,用strlen计算字符串的长度,然后调用sdsnewlen函数创建sds字符串
return sdsnewlen(s, sdslen(s));
}
sdsfree函数
释放sds字符串
void sdsfree(sds s) {
if (s == NULL) return;
zfree(s-sizeof(struct sdshdr));
}
sdsupdatelen函数
更新sds字符串长度
void sdsupdatelen(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); //获取sds对应结构体的起始位置,然后获取free和len
int reallen = (int)strlen(s); //获取字符串真实的长度
sh->free += (sh->len-reallen); //更新剩余空间
sh->len = reallen;
}
sdsclear函数
将sds字符串修改为长度为0的空字符串
void sdsclear(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
sh->free += sh->len;
sh->len = 0;
sh->buf[0] = '\0';
}
sdslen函数
返回sds已使用空间字节数(在sds.h中)
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
sdsavail函数
返回sds未使用空间字节数(在sds.h中)
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}