在redis中,C字符串(以'\0'结尾的字符数组)只用在一些无需对字符串值进行修改的地方,比如打印日志。其他情况,redis使用SDS - SimpleDynamicString 简单动态字符串,来做。
比如
127.0.0.1:6379> set testKey "testValue"
OK
键,是一个字符串对象,底层是一个保存着字符串"testKey"的SDS
值也是一个字符串对象,底层是一个保存着字符串"testValue"的SDS
SDS 定义
struct sdshdr {
// 记录buf数组中已使用的字节数,等同于字符串长度(不包括结尾的\0)
int len;
// 记录buf数组中未使用的字节数
int free;
// 实际保存字符串的字节数组
char buf[];
}
比如一个字符串"test":
len = 4
free = 0(这个不一定,初始时为0,后续说明)
buf[] = 't'、'e'、's'、't'、'\0',注意结尾与C相同,也存在'\0',不记入字符串长度
这样做的优势
1. 常数复杂度获取字符串长度
- C字符串不记录长度,只能遍历,到\0得到长度,时间复杂度O(n),SDS可以直接记录len为长度,时间复杂度O(1)
2. 兼容部分C字符串的函数
- 都遵循C字符串以\0结尾的方式,可以重用一部分C字符串函数库的方法
3. 减少修改字符串时带来的内存重分配次数
- 通过每次增长或减短字符串时,设置free,使字符串预留出一部分空间,而不是每次都精确到字符串的长度。在频繁修改字符串的环境中可以减少内存重新分配的操作
- 具体涉及到两种情况:变长、变短。
- 变长:空间预分配
- 如果修改后的字符串长度小于 1MB,则程序分配大小和len相同的未使用空间给free
- 比如"test"修改为"testAgain",则len=9、free=9、buf[] 数组大小为 9+9+1(\0)=19字节
- 如果修改后的字符串长度大于等于 1MB,则程序分配固定的 1MB给free
- 比如一个0.7MB的字符串扩成了7MB,则len=7MB、free=1MB、buf[] 数组大小为 9MB + 9MB + 1B
- 如果修改后的字符串长度能被len+free放下,则此次修改字符串就不需要重新分配空间了
- 变短:惰性空间释放
- 变短后的字符串,变短的长度被保存在free中,未来如果有变长操作,则可以直接使用。
- 比如"test"修改为"t",则len=1、free=3、buf[] 数组大小为 1+3+1=5字节,与变短之前的4+0+1相同
同时SDS提供实时释放未使用空间的api
其他特点
SDS的api都是二进制安全的,所有SDS的api都会用处理二进制的方式处理buf[]中的数据,不会特殊处理。
比如一个字符串内容为"test\0andmore",其内容不会因为有\0而被截断