SDS(简单动态字符串)是Redis里使用的一种抽象类型。Redis只在字符串不需要更改的场景下才会使用C语言中传统的字符串(例如打印日志时),在其余场景下,Redis均使用自己重新定义的SDS类型来表示字符串
1 SDS结构:
SDS的本质就是一个char类型指针,但是由于其存在一个数据头(sdshdr结构体),所以他可以更好地存储和更改字符串的值,sdshdr结构主要有三块
- int len 记录buf数组中已使用字节数量,等于SDS保存字符串长度
- int free 记录当前字符串未使用的字节数量
- char buf[] 字节数组,保存字符串(以空字符‘\0’结束,但空字符不计入len)
2 SDS优势
2.1 能在O(1)时间复杂度获取字符串长度
这个很好理解,普通的C语言字符串,想要获取它的长度,必须使用for循环遍历记录长度。而SDS的sdshdr结构体中存储了len属性,因此可以直接取到他的长度。
设置和更新SDS长度的工作时由SDSAPI执行时自动完成的,无需使用者手动干预。
2.2 杜绝缓冲区溢出
c语言中传统字符串的另一个问题是,容易造成缓存区溢出,例如当使用strcat(char *dest , const char *src)(将src字符串接到dest字符串后)时,程序员必须保证目标字符串有足够大的容量,否则会出现溢出的错误。
而SDS避免了这种错误,SDS被修改时,API会首先检查SDS的空间是否足够大,若不够大则会修改字符串大小再进行操作,例如SDS的sdscat函数就不会出现溢出的错误。
2.3 减少修改字符串带来的内存重分配次数
这里还是和上面的2.2有关,c语言的字符串和内存里字符数组的长度具有关联性,所以每次增加字符串都要重新进行内存重分配,而SDS由于sdshdr的存在,允许预设一定长度的未使用字节,减少了内存重分配的次数(只有未使用空间不足时才会扩容)。下面主要介绍SDS内存的空间优化策略:
2.3.1 空间预分配
这个类似于java里ArrayList的内存分配(注意,只是类似,ArrayList每次扩容是扩容原始大小的50%+1)在SDSAPI对字符串进行更改需要对字符串扩容时,不仅会扩容这次需要的大小,更会分配额外的未使用空间,其计算方法有两种:
- 若对SDS修改后的长度小于1MB(即len属性的值小于1MB),则会分配len属性长度一样的未使用空间
- 若对SDS修改后的长度大于等于1MB,则会分配1MB的未使用空间
2.3.2 惰性空间释放
SDS字符串在缩短操作时,并不会立即执行内存重分配收回多出来的字节,而是使用free属性把这些字节记录下来等待将来使用
但同时,SDS也提供了API进行真正释放未使用空间,所以不用担心内存浪费
2.4 二进制安全
这个也很好理解,由于SDS字符串不像C语言字符串仅仅用空字符‘\0’来判断字符串结束(SDS有len属性),因此SDS可以保存任意二进制文件。(C语言可能会错将二进制文件中的‘\0’认为成结束标志)
2.5兼容部分C字符串函数
由于SDS的buf字节数组同样遵从了以‘\0’结束的惯例(注意:他并不使用‘\0’来判断结束),因此他可以复用一些C语言的字符串函数代码