redis中的字符串是二进制安全的,传统C字符串以'\0'为结尾,只能用于保存纯文本,不能用于保存音频、视频等二进制文件,因为只要在中间遇到'\0'就被截断了。redis的sds模块所有api都使用二进制方式处理字符串,因此叫做字节数组更为贴切。
数据结构:一个带控制结构的字符串,内存中起始地址是一个控制结构,包含一个柔性数组用来保存字符串,结尾加'\0',使用的时候返回的是字符串起始地址,所以需要获取控制结构中内容的时候用指针减控制结构大小去获取控制结构的起始地址,由于工作在用户态,没法使用container_of,所以经常用a[-1]这样的方式手动去获取控制结构中的内容,通过使用__attribute__((packet))来保证手动算出的偏移没有问题。sds字节数组根据字符串二进制位数使用不同控制结构,包括5,8,16,32,64,其中5不使用,例如字符串位数在8到16之间,那么就使用sdshdr16,sds.h中提供的结构定义如下:
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[];
};
每个结构中,len表示使用的长度,alloc是分配的内存的长度,包含头和\0,flags用3bits标识sds类型,buf是sds内存
sds模块源码在sds.h和sds.c中,sds.h中提供的api:
sdslen:获取一个sds的已经使用的长度
sdsavail:获取一个sds的可以使用的长度,是总长度减去已经使用的长度
sdssetlen:设置sds的已经使用的长度
sdsinclen:增加sds的已经使用的长度
sdsalloc:返回一个sds的分配的内存的长度
sdssetalloc:设置sds的分配的内存的长度
sds.c中提供的api:
sdsHdrSize:计算一个sds类型对应的结构体大小
sdsReqType:根据二进制字符串长度计算应该使用的sds类型
sdsnewlen:创建一个新的sds,会在最后生成一个隐式的\0
sdsempty:创建一个空的sds,因为二进制字符串是空的,因此创建出来的是sds8
sdsnew:根据一个C字符串创建一个对应的sds
sdsdup:复制一个sds
sdsfree:释放一个sds
sdsupdatelen:将sds的已经使用的长度修改成第一个'\0'结尾的字符串的长度,相当于从第一个\0截断
sdsclear:将sds的内存清零但不释放
sdsMakeRoomFor:给一个sds扩充空间以供以后使用
sdsRomoveFreeSpace:根据实际使用长度减少sds的分配内存
sdsAllocSize:返回一个sds的总长度,包括头,二进制字符串分配的内存长度和'\0'
sdsAllocPtr:返回一个sds的控制结构的起始地址
sdsIncrLen:增加一个sds的已经使用的长度,要求加完之后不能超过分配的内存长度
sdsgrowzero:给一个sds的已经使用长度增加到指定长度,并将增加部分内存清零
sdscatlen:在一个sds后面连接给定长度的二进制字符串
sdscat:在一个sds后面连接一个C字符串
sdscatsds:将一个sds的已经使用的内存连接到另一个sds中
sdscopylen:将一个给定长度的二进制字符串复制到sds,并更新已经使用的长度
sdscpy:将一个C字符串复制到sds
sdsll2str:将一个long long转换成C字符串格式
sdsull2str:将一个unsigned long long 转换成C字符串格式
sdsfromlonglong:使用long long制造一个sds
sdscatvprintf:将可变长格式化字符串连接到sds后面
sdscatprintf:是sdscatvprintf的包装
sdscatfmt:和sdscatvprintf功能一样,区别是不使用sprintf族函数,性能高
sdstrim:从sds的头和尾去掉所有和给定字符一致的内容,保留剩下的部分
sdsrange:将sds切片,提供起始和终止位置,可以是负数表示从结尾开始
sdstolower:将sds的所有字母变成小写,内部是tolower
sdstoupper:将sds的所有字母变成大写,内部是toupper
sdscmp:比较两个sds,内部是memcmp
sdssplitlen:将字符串用指定的分割器分割成若干部分,存入sds指针数组,并返回这个指针数组,是简单的字符串匹配,可以使用KMP优化
sdsfreesplitres:释放sdssplitlen产生的指针数组和其中的sds的内存
sdscatrepr:处理包含转义字符的二进制字符串
is_hex_digit:判断char c是否是十六进制数,是根据ASCII码判断的
hex_digit_to_int:将一个十六进制字符转换成十进制
sdssplitargs:将一行内容读取到sds指针数组,解析配置文件时使用,每个配置项保存在一个sds中,此函数返回一个sds指针数组和其元素个数
sdsmapchars:将sds中出现在指定字符集中的字符用另一个字符集中的字符代替,要求两个字符集长度相等
sdsjoin:将若干C字符串用提供的分割器连接起来,产生一个sds
sdsjoinsds:和sdsjoin类似,连接的是若干sds
sds_malloc:分配内存
sds_realloc:重新分配内存
sds_free:释放内存