简单动态字符串
有5种类型,分别对应不同长度的sds: sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64
使用__attribute__ ((__packed__))定义sdshdr,告诉编译器取消字节对齐,结构体成员在内存空间上都是连续的,使用sds的小标可以直接指向sdshdr中的其他成员
sdshdr
len :sds已使用的长度
alloc:包括头部与已使用+未使用的空间占用的大小
flags:标识sdshdr类型
char buf[]:sds,柔性数组,起到标记的作用,标识在flags字段后边是一个字符数组,在sdshdr分配内存的时候,柔性数组并不会占用内存
因为buf指向sda,所以通过sds和sizeof(sdshdr)可以获得sdshdr结构体首地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
sds[-1] :flags & 7 :sds_type
SDS_HDR(sds_type, sds): sdshdr结构体首地址
SDS_HDR(sds_type, sds)->len: 获得sds长度
SDS的一些基础函数
sdslen(const sds s): 获取sds字符串长度。
sdssetlen(sds s, size_t newlen): 设置sds字符串长度。
sdsinclen(sds s, size_t inc): 增加sds字符串长度。
sdsalloc(const sds s): 获取sds字符串容量。
sdssetalloc(sds s, size_t newlen): 设置sds字符串容量。
sdsavail(const sds s): 获取sds字符串空余空间(即alloc - len)。
sdsHdrSize(char type): 根据header类型得到header大小。
sdsReqType(size_t string_size): 根据字符串数据长度计算所需要的header类型。
sds sdsnewlen(const void *init, size_t initlen):sds创建
一次性进行分配需要的内存空间,包含三部分:header、数据、最后的多余字节(hdrlen+initlen+1)
初始化的sds字符串数据最后要追加一个NULL结束符s[initlen] = '\0'
void sdsfree(sds s):sds释放
需要注意的是:内存要整体释放,所以要先计算出header起始指针,把它传给s_free函数。
(char*)s-sdsHdrSize(s[-1]) 这个指针也正是在sdsnewlen中调用s_malloc返回的那个地址
sds sdscatlen(sds s, const void *t, size_t len):sds追加
将t指向的长度为len的任意二进制数据追加到sds字符串s的后面。
sds sdsMakeRoomFor(sds s, size_t addlen):
保证字符串s有足够的空间来追加长度为len的数据,可能会分配新的内存,也可能不会
按分配后的空间大小,可能需要更换header类型(原来header的alloc字段太短,表达不了增加后的容量)
如果不需要更换header,调用s_realloc,试图在原来的地址上重新分配空间,尽量在原来分配好的地址位置重新分配,如果原来的地址位置有足够的空余空间完成重新分配,那么它返回的新地址与传入的旧地址相同;否则,它分配新的地址块,并进行数据搬迁
如果需要更换header,那么整个字符串空间(包括header)都需要重新分配(s_malloc),并拷贝原来的数据到新的位置
调用sdscatlen的时候,传入一个旧的sds变量,然后它返回一个新的sds变量。由于它的内部实现可能会造成地址变化,因此调用者在调用完之后,原来旧的变量就失效了,而都应该用新返回的变量来替换。sds中的其它函数(比如sdscpy、sdstrim、sdsjoin等),还有Redis中其它一些能自动扩展内存的数据结构(如ziplist),也都是同样的使用模式