redis源码解析——SDS(简单动态字符串)
Posted A_BCDE_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis源码解析——SDS(简单动态字符串)相关的知识,希望对你有一定的参考价值。
版本:redis - 5.0.4
参考资料:redis设计与实现
文件:src下的sds.c sds.h
一、c语言中的字符串 vs SDS
c中无字符串,只有字符数组
//c语言中定义一个字符串
//申请一段连续数组空间,s指向首地址,每个单元存储一个字符,'\\0'为结束标志
// 'h','e','l','l','o','\\0'
char* s = "hello"
//sds
struct __attribute__ ((__packed__)) sdshdr8
uint8_t len; //已使用的数组长度,不包括零结束标志
uint8_t alloc; //总长度,不包括零结束标志
unsigned char flags; //表示选择哪种sds header
char buf[];//用于存储字符串
;
- 获取字符串长度需要运算:没有字段存储字符串长度,需要运算。申请空间大小≠字符串长度。
- 非二进制安全:C语言中,以空字符‘\\0’作字符串结束标志。
- sds延续了这种做法,兼容部分c字符串函数。
- 字符串里不能包含空字符。sds以处理二进制的方式处理数据。
- 不可修改:sds同样用字符数组存储字符串。字符串修改时,字符串增长或缩短,内存需要重分配:
- 空间预分配:字符串增长时并需要空间扩展时,若增长后的 len < 1M,则再分配同样大小的avail空间,也就是说buf数组的实际长度变为len*2 + 1字节(一字节存储空字符)。若增长后的len >= 1M,则再分配1M大小的avail空间,buf数组的实际长度变为len + 1M + 1byte;
- 惰性空间释放:字符串缩短时,并不释放多出来的字节,而是记录下来,等待将来使用。
c字符串 | sds |
---|---|
获取字符串长度的复杂度为 O(N) | 获取字符串长度的复杂度为 O(1) |
API 是不安全的,可能会造成缓冲区溢出 | API 是安全的,不会造成缓冲区溢出 |
修改字符串长度 N 次必然需要执行 N 次内存重分配 | 修改字符串长度 N 次最多需要执行 N 次内存重分配 ,内存分配次数减少,可动态扩容 |
只能保存文本数据 | 可以保存文本或者二进制数据 |
可以使用所有 <string.h> 库中的函数 | 可以使用一部分 <string.h> 库中的函数 |
二、sds.h
1、sdshdr
header
sdshdr(sds header)用于控制字符串,存储了字符数组buf,字符串长度len(实际使用的的长度),总长度alloc(数组长度减去存放空字符的1的字节)。
由于字符串长度不同,所以表示长度的len和alloc所需要的大小也不同,有四种:
- 1字节 uint8_t
- 2字节 uint16_t
- 4字节 uint32_t
- 8字节 uint64_t
关于 size_t 的更多知识
由此产生了五种header ,通过选择合适的header,用来节省空间。并且header里需要存储一个flag,表明选择了哪种header。flag占一个字节,8位,而五种header只需用三位表示就行,所以还有五位没有用。
__attribute__((packed)) :编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。
__attribute__可以设置函数属性、变量属性和类型属性
使用格式为:__attribute__ ((attribute-list));要求放于声明的尾部“;”之前。
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
* 将该地址赋给了一个新变量sh,得到了一个指向header的指针。
* T是type(哪一种header),s是字符串指针(一个字符串的首地址)
* sizeof(struct sdshdr##T)是一个header大小(不包括buf),
* (s)-(sizeof(struct sdshdr##T))就是首地址减header的大小,即header的首地址。
*/
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
//得到header的首地址
#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)
2、static inline 函数
下面,在.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;
3、SDS API
最后声明了一些操作:
//创建一个(定长),包含给定字符串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地址
三、sds.c
主要是一些对字符串的增加删除,空间申请释放
1、sdsIncrLen
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 = 0sds数据结构定义
在sds.h文件中,我们可以找到sds的数据结构定义如下:
typedef char *sds;
看到这里可能大家都疑惑了,这不就是char\\*嘛?的确,Redis采用一整段连续的内存来存储sds结构,char\\*类型正好可以和传统的C语言字符串类型兼容。但是,sds和char*并不等同,sds是二进制安全的,它可以存储任意二进制数据,不能像C语言字符串那样以‘\\0’来标识字符串结束,因此它必然存在一个长度字段,那么这个字段在哪呢?请看下面的代码:
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
;
struct __attribute__ ((__packed__)) sdshdr8
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
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[];
;
sds结构一共有五种Header定义,其目的是为了满足不同长度的字符串可以使用不同大小的Header,从而节省内存。 Header部分主要包含以下几个部分: + len:表示字符串真正的长度,不包含空终止字符 + alloc:表示字符串的最大容量,不包含Header和最后的空终止字符 + flags:表示header的类型
// 五种header类型,flags取值为0~4
#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是采用一段连续的内存空间来存储动态字符串,那么,我们进一步来分析一下sds在内存中的布局。下图是字符串”redis”在内存的布局示例图,
由于sds的header共有五种,要想得到sds的header属性,就必须先知道header的类型,flags字段存储了header的类型。假如我们定义了sds* s,那么获取flags字段仅仅需要将s向前移动一个字节,即unsigned char flags = s[-1]。
在这里解释一下attribute ((packed))的用意:加上此字段是为了让编译器以紧凑模式来分配内存。如果没有这个字段,编译器会按照struct中的字段进行内存对齐,这样的话就不能保证header和sds的数据部分紧紧的相邻了,也不能按照固定的偏移来获取flags字段。
获取了header的类型之后,我们就可以依照每个类型header的定义来获取sds的长度,最大容量等属性了。Redis定义了如下几个宏定义来操作header
#define SDS_TYPE_MASK 7 // 类型掩码
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); // 获取header头指针
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) // 获取header头指针
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 获取sdshdr5的长度
这里需要注意宏定义中的##是将两个符号连接成一个,如sdshdr和8(T为8)合成sdshdr8
其中SDS_HDR是为了获取header的头指针,即s指针按照header的结构大小向前偏移sizeof(struct sdshdr##T)位,找到了header的头指针,就很容易获取len和alloc的大小了。
到这里,sds的数据结构定义就基本清楚了,下面来看看sds的一些基本操作函数。
sds基本操作函数
sds创建函数
Redis在创建sds时,会为其申请一段连续的内存空间,其中包含sds的header和数据部分buf[]。其创建函数如下:
sds sdsnewlen(const void *init, size_t initlen)
void *sh;
sds s;
char type = sdsReqType(initlen);
// 空的字符串通常被创建成type 8,因为type 5已经不实用了。
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
// 得到sds的header的大小
int hdrlen = sdsHdrSize(type);
unsigned char *fp; // flags字段的指针
// s_malloc等同于zmalloc,+1代表字符串结束符
sh = s_malloc(hdrlen+initlen+1);
if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
// s为数据部分的起始指针
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1; // 得到flags的指针
// 根据字符串类型来设定header中的字段
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'; // 与C字符串兼容
return s; // 返回创建的sds字符串指针
sds释放函数
sds的释放采用zfree来释放内存。其实现代码如下:
void sdsfree(sds s)
if (s == NULL) return;
// 得到内存的真正其实位置,然后释放内存
s_free((char*)s-sdsHdrSize(s[-1]));
sds动态调整函数
sds最重要的性能就是动态调整,Redis提供了扩展sds容量的函数。
// 在原有的字符串中取得更大的空间,并返回扩展空间后的字符串
sds sdsMakeRoomFor(sds s, size_t addlen)
void *sh, *newsh;
size_t avail = sdsavail(s); // 获取sds的剩余空间
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
// 如果剩余空间足够,则直接返回
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
// sds规定:如果扩展后的字符串总长度小于1M则新字符串长度为扩展后的两倍
// 如果大于1M,则新的总长度为扩展后的总长度加上1M
// 这样做的目的是减少Redis内存分配的次数,同时尽量节省空间
if (newlen < SDS_MAX_PREALLOC) // SDS_MAX_PREALLOC = 1024*1024
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
// 根据sds的长度来调整类型
type = sdsReqType(newlen);
// 不使用SDS_TYPE_5,一律按SDS_TYPE_8处理
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
// 获取新类型的头长度
hdrlen = sdsHdrSize(type);
if (oldtype==type)
// 如果与原类型相同,直接调用realloc函数扩充内存
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
else
// 如果类型调整了,header的大小就需要调整
// 这时就需要移动buf[]部分,所以不能使用realloc
newsh = s_malloc(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen; // 更新s
s[-1] = type; // 设定新的flags参数
sdssetlen(s, len); // 更新len
sdssetalloc(s, newlen); // 更新sds的容量
return s;
另外,Redis还提供了回收sds空余空间的函数。
// 用来回收sds空余空间,压缩内存,函数调用后,s会无效
// 实际上,就是重新分配一块内存,将原有数据拷贝到新内存上,并释放原有空间
// 新内存的大小比原来小了alloc-len大小
sds sdsRemoveFreeSpace(sds s)
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
size_t len = sdslen(s); // 获取字符串的实际大小
sh = (char*)s-sdsHdrSize(oldtype);
type = sdsReqType(len);
hdrlen = sdsHdrSize(type);
if (oldtype==type)
newsh = s_realloc(sh, hdrlen+len+1); // 申请的内存大小为hdrlen+len,原有的空余空间不算
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
else
newsh = s_malloc(hdrlen+len+1); // 如上
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
sdssetalloc(s, len);
return s;
sds连接操作函数
sds提供了字符串的连接函数,用来连接两个字符串
sds sdscatlen(sds s, const void *t, size_t len)
size_t curlen = sdslen(s); // 获取当前字符串的长度
s = sdsMakeRoomFor(s,len); // 扩展空间
if (s == NULL) return NULL;
memcpy(s+curlen, t, len); // 连接新字符串
sdssetlen(s, curlen+len); // 设定连接后字符串长度
s[curlen+len] = '\\0';
return s;
sds其他操作函数
sds还提供了一系列的操作函数,这里就不列出源码,只说明其用途。
sds sdsempty(void); // 清空sds
sds sdsdup(const sds s); // 复制字符串
sds sdsgrowzero(sds s, size_t len); // 扩展字符串到指定长度
sds sdscpylen(sds s, const char *t, size_t len); // 字符串的复制
sds sdscpy(sds s, const char *t); // 字符串的复制
sds sdscatfmt(sds s, char const *fmt, ...); //字符串格式化输出
sds sdstrim(sds s, const char *cset); //字符串缩减
void sdsrange(sds s, int start, int end); //字符串截取函数
void sdsupdatelen(sds s); //更新字符串最新的长度
void sdsclear(sds s); //字符串清空操作
void sdstolower(sds s); //sds字符转小写表示
void sdstoupper(sds s); //sds字符统一转大写
sds sdsjoin(char **argv, int argc, char *sep); //以分隔符连接字符串子数组构成新的字符串
sds小结
sds是Redis中最基本的数据结构,使用一整段连续的内存来存储sds头信息和数据信息。其中,字符串的header包括了sds的字符串长度,字符串的最大容量以及sds的类型这三大信息。这样做的好处有很多,能让很多操作的复杂度降低,比如获取sds中字符串长度的操作,只需要O(1)即可,比strlen的O(N)好很多。
另外,sds还提供了很多操作函数,使其在拥有原生字符串的特性外,还能动态扩展内存和符合二进制安全等。
以上是关于redis源码解析——SDS(简单动态字符串)的主要内容,如果未能解决你的问题,请参考以下文章