版本:redis - 5.0.4
参考资料:redis设计与实现
文件:src下的sds.c sds.h
c语言中本身就可以处理字符串,为什么还要自己构建sds?二者有什么区别?
c字符串 | sds |
---|---|
获取字符串长度的复杂度为 O(N) | 获取字符串长度的复杂度为 O(1) |
API 是不安全的,可能会造成缓冲区溢出 | API 是安全的,不会造成缓冲区溢出 |
修改字符串长度 N 次必然需要执行 N 次内存重分配 | 修改字符串长度 N 次最多需要执行 N 次内存重分配 |
只能保存文本数据 | 可以保存文本或者二进制数据 |
可以使用所有 |
可以使用一部分 |
sdshdr(sds header)用于控制字符串,存储了字符数组buf,字符串长度len(实际使用的的长度),总长度alloc(数组长度减去存放空字符的1的字节)。
由于字符串长度不同,所以表示长度的len和alloc所需要的大小也不同,有四种:
由此产生了五种header ,通过选择合适的header,用来节省空间。并且header里需要存储一个flag,表明选择了哪种header。flag占一个字节,8位,而五种header只需用三位表示就行,所以还有五位没有用。
__attribute__((packed)) :编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。
__attribute__可以设置函数属性、变量属性和类型属性
使用格式为:__attribute__ ((attribute-list));要求放于声明的尾部“;”之前。
关于 __attribute__的更多内容
typedef char *sds;
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
/*注意: 永远不会使用 sdshdr5, 我们只需直接访问标志字节。但是, 这里是文件类型 5 SDS 字符串的布局。*/
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; //3位存header类型, (高)5位存字符长度
char buf[];//字符最长为2^5 = 31
//所以sdshdr5 没有alloc,
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; //已使用的数组长度
uint8_t alloc; //总长度不包括零结束标志
unsigned char flags; //表示选择哪种sds header
char buf[];//用于存储字符串
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
//一共五种header 用三位表示
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
此处需要说明sds的存储:
大家可以发现由于在一个结构体里面,而且关闭了自动对齐,此时的header内的各个部分都是紧紧挨在一起的,没有多余空间。
在一开始申请空间时,buf只是一个标志,证明那有一个数组,但并不给其分配空间,只给其他的部分分配空间。
然后宏定义了一些操作:
//宏定义中的##是将两个符号连接成一个,如T=5时,sdshdr和T合成sdshdr5
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
//f为flag, f左移三位,得到高五位,就是字符长度
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
T是type(哪一种header),s是字符串指针(一个字符串的首地址)
sizeof(struct sdshdr##T)是一个header大小(不包括buf),(s)-(sizeof(struct sdshdr##T))就是首地址减header的大小,如图:
所以得到的是header的首地址。SDS_HDR(T,s)得到了header的首地址(其实也是指针),而SDS_HDR_VAR(T,s)是将该地址赋给了一个新变量sh,得到了一个指向header的指针。
下面,在.h文件中定义了一些函数,这些函数是static inline的。
inline:把函数指定为内联函数。
#define SDS_TYPE_MASK 7//掩码
#define SDS_TYPE_BITS 3
//得到s的长度
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1];
//flag和掩码相与,只留后三位,判断类型
//根据类型,得到header,获取长度
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}
//得到剩余的空间
static inline size_t sdsavail(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5: {
return 0;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
return sh->alloc - sh->len;
}
}
return 0;
}
//设置一个新长度
static inline void sdssetlen(sds s, size_t newlen) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{
//s-1的位置就是flag
unsigned char *fp = ((unsigned char*)s)-1;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen;
break;
}
}
//长度增加:添加字符串后,长度增加
static inline void sdsinclen(sds s, size_t inc) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc;
break;
}
}
/* sdsalloc() = sdsavail() + sdslen() */
//总空间大小
static inline size_t sdsalloc(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->alloc;
case SDS_TYPE_16:
return SDS_HDR(16,s)->alloc;
case SDS_TYPE_32:
return SDS_HDR(32,s)->alloc;
case SDS_TYPE_64:
return SDS_HDR(64,s)->alloc;
}
return 0;
}
//设置总空间大小
static inline void sdssetalloc(sds s, size_t newlen) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
/* Nothing to do, this type has no total allocation info. */
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen;
break;
}
}
最后声明了一些操作:
//创建一个(定长),包含给定字符串init的sds
sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty(void);//创建一个空sds
sds sdsdup(const sds s);//复制sds
void sdsfree(sds s);//释放sds的header
sds sdsgrowzero(sds s, size_t len);//用空字符将s扩展到给定长
//把t(的len个字符)加到s之后
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
sds sdscatsds(sds s, const sds t);
//把t(的len个字符),复制到s中,并覆盖原来的
sds sdscpylen(sds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t);
//将ap按照给定格式fmt打印后的字符串,添加到s中
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
//如果__GNUC__被宏定义过,则执行第一部分,否则执行第二部分
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
sds sdscatprintf(sds s, const char *fmt, ...);
#endif
sds sdscatfmt(sds s, char const *fmt, ...);//类似于sdscatprintf,但只处理打印格式标识符
//format (archetype, string-index, first-to-check)
//archetype:哪种风格;
//string-index:传入函数的第几个参数是格式化字符串;
//first-to-check:从函数的第几个参数开始按格式化字符串的规则进行检查。
sds sdstrim(sds s, const char *cset);//从左右两端删除除指定字符串cset外的全部字符
void sdsrange(sds s, ssize_t start, ssize_t end);//截取字符串
void sdsupdatelen(sds s);//以第一个空格为界,得到字符串长度,重置
void sdsclear(sds s);//清空sds字符串
int sdscmp(const sds s1, const sds s2);//比较字符串
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);//用sep分割字符串s分割后存在数组中返回,数组元素个数为count
void sdsfreesplitres(sds *tokens, int count);//释放数组tokens的count个元素
sds *sdssplitargs(const char *line, int *argc);//将line分割,分割后的元素数为argc
void sdstolower(sds s);//变小写
void sdstoupper(sds s);//变大写
sds sdsfromlonglong(long long value);//把long long类型转换为sds
sds sdscatrepr(sds s, const char *p, size_t len);//处理p中的所有不可打印字符后,增加到s后
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);//s中的from字符换为to字符
//将argv数组中的内容用sep拼接成一个字符串,(放在sds)
sds sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
/* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen);//给末尾增大空间
void sdsIncrLen(sds s, ssize_t incr);//增加或减少长度
sds sdsRemoveFreeSpace(sds s);//紧缩空间
size_t sdsAllocSize(sds s);//sds的得到全部大小
void *sdsAllocPtr(sds s);//得到header地址
大多数都是一些对字符串的增加删除,空间申请释放,基本都能看懂,下面看几个比较麻烦的函数。
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,
然后通过调用 abort 来终止程序运行。
已放弃使用assert()的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
/*
sds 字符串长度增加,空余位置减少,在字符串末尾设空字符。
此函数用于用户调用 Sdsmakecomfor ()修改字符串长度后, 写完当前字符串的内容, 最后需要设置新的长度。
注意: 可以使用负增量来修剪字符串。
*/
void sdsIncrLen(sds s, ssize_t incr) {
unsigned char flags = s[-1];
size_t len;
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5: {
unsigned char *fp = ((unsigned char*)s)-1;
unsigned char oldlen = SDS_TYPE_5_LEN(flags);
assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
*fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);
len = oldlen+incr;
break;
}
......//此处省略部分相似代码,类似的还有 SDS_TYPE_8, SDS_TYPE_16, SDS_TYPE_32, SDS_TYPE_64
default: len = 0; /* Just to avoid compilation warnings. */
}
s[len] = '\0';
}
//把unsigned long long类型的值转为字符串并存储
int sdsull2str(char *s, unsigned long long v) {
char *p, aux;
size_t l;
/* Generate the string representation, this method produces
* an reversed string. */
p = s;
do {
*p++ = '0'+(v%10);//这样从个位放起,结果是反的,所以最后要反转字符串
v /= 10;
} while(v);
/* Compute length and add null term. */
l = p-s;
*p = '\0';
/* Reverse the string. */
p--;
while(s < p) {
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
return l;
}
type va_arg(va_list argptr, type);
void va_end(va_list argptr);
void va_start(va_list argptr, last_parm);
任何可变长度的变元被访问之前,必须先用va_start()初始化变元指针argptr。
初始化argptr后,经过对va_arg()的调用,以作为下一个参数类型的参数类型,返回参数。
最后取完所有参数并从函数返回之前。必须调用va_end()。由此确保堆栈的正确恢复。
其实就是堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程.
/*
把参数按指定格式输出,添加到s中
*/
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*2;
//先用尝试静态缓冲区staticbuf,如果大小不够,再用堆分配
if (buflen > sizeof(staticbuf)) {
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
}
//先用,如果大小不够,申请两倍大的空间
while(1) {
buf[buflen-2] = '\0';
va_copy(cpy,ap);
//把cpy按照fmt格式输出到buf中,可接收的最大字符数量为buflen
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
if (buf[buflen-2] != '\0') {//长度超出
if (buf != staticbuf) s_free(buf);//释放buf
buflen *= 2;
buf = s_malloc(buflen);//申请两倍大的空间
if (buf == NULL) return NULL;
continue;
}
break;
}
//把buf的值,加到s后面,返回新下标
t = sdscat(s, buf);
if (buf != staticbuf) s_free(buf);
return t;
}
/*count:分隔后的元素个数*/
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {
int elements = 0, slots = 5;//elements实际元素数;默认元素slots个数(数组默认大小)为5
long start = 0, j;
sds *tokens;//分隔后的结果存储在此数组中
if (seplen < 1 || len < 0) return NULL;
tokens = s_malloc(sizeof(sds)*slots);
if (tokens == NULL) return NULL;
if (len == 0) {
*count = 0;
return tokens;
}
for (j = 0; j < (len-(seplen-1)); j++) {
//数组大小不够,重新申请两倍空间(快,但浪费空间)
if (slots < elements+2) {
sds *newtokens;
slots *= 2;
newtokens = s_realloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) goto cleanup;
tokens = newtokens;
}
//分割元素
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
tokens[elements] = sdsnewlen(s+start,j-start);//找到对应字符串,分割并存储
if (tokens[elements] == NULL) goto cleanup;//如果为null,释放
elements++;
start = j+seplen;
j = j+seplen-1; /* skip the separator */
}
}
//把最后剩下的存储
tokens[elements] = sdsnewlen(s+start,len-start);
if (tokens[elements] == NULL) goto cleanup;
elements++;
*count = elements;
return tokens;
cleanup:
{
int i;
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
s_free(tokens);
*count = 0;
return NULL;
}
}
这个不难,只是被频繁使用
/*
使用init和initlen指定的内容创建一个新的sds字符串。
init = null, 则字符串将以零字节初始化。
init = SDS _ NOINIT, 则缓冲区不初始化,sds字符串始终为空终止;
您可以使用 printf () 打印字符串, 因为字符串的末尾有一个隐式 \ 0。
该字符串是二进制安全的, 可以包含\ 0 字符的中间, 因为长度存储在sds头中。
*/
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
//获取信息
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */
//申请空间
sh = s_malloc(hdrlen+initlen+1);
//填充header信息
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
//初始化字符串
if (initlen && init)
memcpy(s, init, initlen);//拷贝添加字符串
s[initlen] = '\0';
return s;
}