Sds(Simple Dynamic String) 是redis底层所使用的字符串
1.1 用途
- 实现字符串对象
- 替代
char*
类型
1.2 结构
sds如下所示,只是普通的char指针
1 | typedef char *sds |
而 redis 使用[sds header(sdshdr) + char *] 的形式来存放一个字符串
sdshdr 存放的是字符串的相关信息, 一共有5种不同的类型
1 | typedef char *sds; |
之所以有5种,是为了能让不同长度的字符串可以使用不同大小的header。这样,短字符串就能使用较小的header,从而节省内存, 这点我们可以从每一种header中的 len 的类型可以看出
在每一个 sdshdr[n] 中,最后一个成员是一个柔性数组(flexible array)buf,该数组相当于一个标志,并不占用该结构体的空间,在某些编译器下,柔性数组的使用方式是声明一个长度为0的数组 a[0]
在sdshdr的声明中,有个编译选项 __attribute__ ((__packed__))
在加入该选项之后,结构体就不会进行对齐,而是紧凑压缩,结构体的大小等于内部成员的大小之和
1.3 相关的宏
1 |
- SDSHDRVAR(T,s) : 声明一个sdshdr的结构体指针sh,指向s所在的sdshdr
- SDSHDR(T,s) : 获得s所在的sdshdr,不声明变量
- SDSTYPE5LEN : 获得sdshdr5的长度,sdshdr5的长度在flags里面,flags有8位,前3位作为类型,后5位作为长度,详情见 sdshdr 的注释
1.4 相关操作
1.4.1 获取sds的长度
由于flag的前三位是sds的长度, 因此,首先要获得sdshdr的flags,s[-1]获得的正是flags,随后将flags与SDSTYPEMASK进行按位与操作,获得flags的前三位,得到该sdshdr的类型
之所以进行按位与操作,是因为sdshdr5的特殊性
之后,根据不同的sdstype使用SDSHDR来进行相应的sdshdr获取(主要是不同的type,len和alloc的类型不同,因此需要进行不同距离的指针偏移)
1 | static inline size_t sdslen(const sds s) { |
1.4.2 获得sds的容量
由于预分配策略,除了sdstype5外的其他sds的容量并不等于当前长度,因此要使用sdsalloc获取sds的容量,操作与sdslen差不多
1 | static inline size_t sdsalloc(const sds s) { |
1.4.3 其他类似的函数
1 | static inline size_t sdsavail(const sds s) //获取剩余长度,用alloc - len |
- Q:重新设置长度/容量为什么不需要重新分配空间?
- A:因为这些函数的功能只是修改某个属性,真正对内存空间进行修改的是调用它们的函数
1.4.4 核心操作
使用现有字符串创建新的字符串
该函数的使用方式为 mystring = sdsnewlen("abc", 3);
该函数使用 sdsReqType 获取现有字符串 init
的 sdstype , 该函数利用字符串的长度进行类型的选择
然后利用 sdsHdrSize 获取相应 sds hdr 的长度(也就是 sizeof(sds hdr type))
接着调用 s_malloc(zmalloc)
为整个 sds(sds hdr + char *) 分配内存
在 zmalloc
中,先调用 malloc
进行内存的分配,如果分配失败则中断程序,如果分配成功则精确地更新 used_memory
变量维护实际分配的内存大小
为什么要说精确的呢?因为 malloc 会在分配内存的时候进行内存对齐,因此我们可能分配到的内存会比我们想要的内存大那么一些,因此需要对多出来的部分进行一个计算然后才能更细全局的已分配内存大小
1 | void *zmalloc(size_t size) { |
接下来检查 sds,如果 sds 不是空指针而且不等于 SDS_NOINIT
,那么就将所分配的空间进行清零
然后根据 init 的 sds hdr 类型填充所分配的空间的 sds hdr 部分,最后拷贝 init 所指向的字符串
1 | /* Create a new sds string with the content specified by the 'init' pointer |
扩容
扩容的话其实需要注意的就是 sdshdr
类型在扩容之后的改变
如果 sdshdr
没有改变,那么就使用 realloc
直接修改内存大小
如果改变了,那么就重新设置新的 sdshdr
1 | sds sdsMakeRoomFor(sds s, size_t addlen) { |
回收 sds 中的未使用空间
既然是回收空间,那么修改的地方就是 sdshdr
1 | /* Reallocate the sds string so that it has no free space at the end. The |