最近开始看redis源码,本来想直接看redis源码设计与实现,这是一本国人写的不错的源码剖析书,介绍了redis的原理,我准备自己来写下redis源码的注释 也是学习
先看sds这个文件 ,sds本质是一个动态字符串但是不是以'\0'结束,这个文件很简单基本看下内存布局就知道作用
struct sdshdr {
unsigned int len; //已经用了的长度
unsigned int free; //还有多少长度可以用
char buf[]; //数据区
};
这是典型的一个c技巧 buf 是一个不定长数组
内存布局如下
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
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)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
用法如下 mystring = sdsnewlen("abc",3); mystring 指向了数据
函数功能就是创建了一个新的包含init 和initlen长度的动态字符串 假如 initlen大于init那鬼知道会发生什么
比如mystring = sdsnewlen("ab",3);
sds sdsempty(void) {
return sdsnewlen("",0);
}
char *ret = sdsempty()
内存布局如下
sds sdsdup(const sds s) {
return sdsnewlen(s, sdslen(s));
}
复制一份同样的数据到新的字符串
void sdsupdatelen(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
int reallen = (int)strlen(s); WIN_PORT_FIX /* cast (int) */
sh->free += (sh->len-reallen);
sh->len = reallen;
}
这个函数功能也很简单 就是按照'\0'进行截断
比如有一个sds s = sdsnew("foobar");
那这时候取他的长度 是6
那强制 s[2] = '\0'取长度还是6 因为取长度是根据len来决定 ,这个函数重新根据设置了len
调用这个函数后再取长度就是2了
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
newsh->free = (int)(newlen - len); WIN_PORT_FIX /* cast (int) */
return newsh->buf;
}
这里主要是动态扩展,当新增的长度free不够时候,看需要的内存是不是超过1M,超过直接分配1M + 需要的内存 否则直接翻倍
void sdsIncrLen(sds s, int incr) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
if (incr >= 0)
assert(sh->free >= (unsigned int)incr);
else
assert(sh->len >= (unsigned int)(-incr));
sh->len += incr;
sh->free -= incr;
s[sh->len] = '\0';
}
函数功能很简单, 就是扩大或者缩小对应的使用,并且在使用长度填充'\0' 所以会改变原来的内容
sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;
if (len <= curlen) return s;
s = sdsMakeRoomFor(s,len-curlen);
if (s == NULL) return NULL;
/* Make sure added region doesn't contain garbage */
sh = (void*)(s-(sizeof(struct sdshdr)));
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
totlen = sh->len+sh->free;
sh->len = (int)len; WIN_PORT_FIX /* cast (int) */
sh->free = (int)(totlen-sh->len); WIN_PORT_FIX /* cast (int) */
return s;
}
函数功能是 在s后面添加len个0 假如空间不够重新申请内存
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len);
sh->len = (int)(curlen+len); WIN_PORT_FIX /* cast (int) */
sh->free = (int)(sh->free-len); WIN_PORT_FIX /* cast (int) */
s[curlen+len] = '\0';
return s;
}
和c标准库功能类似 strcat 拼接数据但是不限于字符串 所以可以存储二进制数据
还有一些函数 功能都很简单 这里可以总结下这个文件作用
解决了二进制存储的问题
缓冲区溢出问题 而且不用管理字符串内存 当然需要管理sds 每次返回去的都是数据指针,可以直接当作字符串使用 比如打印只要符合c语言字符串结束符
为了性能考虑 每次分配内存会根据需要内存大小尽量多分配一些 避免增长时候重新分配内存 减少内存分配调用
当不需要内存时候也可以通过sdsRemoveFreeSpace来缩小内存。