Redis中自定义了简单动态字符串,兼容传统C字符串,并提供了一些高效、安全的方法,用于Redis键值对中需要字符串的存储。除了一些日志输出的字面量外,其余需要修改的字符串都以sds作为存储。
1. 定义
1 | typedef char* sds; |
sds是char *的别名,指向了存储字符串数组的首地址。
sdshdr是存储的真正结构,包含len, alloc, flags, buf字段。
- len: 字符串长度,不包含终止字符串’\0’
- alloc: 字符串最大容量,不包含header和最后的’\0’
- flags: 低3bit用于表示header类型,高5bit未使用(sdshdr5中高5bit用于存储字符串长度)
- buf: 存放字符串的内存空间,sds指向该空间的首地址
为了节省内存,Redis实现了不同大小的sdshdr,用于存储不同长度的字符串
- sdshdr5: 0 ~ 31时使用,没有len,alloc字段,用flags的高5位表示字符串长度
- sdshdr8: 32 ~ 255 时使用
- sdshdr16: 256 ~ 65535 使用
- sdshdr32: 64位机器中,65536 ~ 2^32-1 使用
- sdshdr64: 32位机器中大于65535、64位机器中大于 2^32-1 使用
使用packed属性取消编译器对结构体的字节对齐
2. 优势
获取字符串长度 O(1)
传统C字符串使用‘\0’表示字符结尾,使用strlen(s)获取字符串长度,遍历字符串数组并计数,遇到’\0’表示结束并输出字符串长度,时间复杂度为O(N),SDS字符串在结构体里存储了字符串长度,获取为常数时间O(1)。
避免缓存区溢出
传统C字符串使用strcat拼接时,由于不清楚字符串长度,可能会使用到非分配的内存空间,导致内存溢出,SDS字符串API保证了拼接字符串时若剩余空间不足,则会先分配内存空间,再进行拼接。
减少内存分配次数
传统C字符串进行修改时,会重新分配内存空间,再进行数据拷贝,而SDS字符串采用预分配的方式,每次分配内存大小是需要的2倍(但每次分配不能大于1M,还有额外的’\0’),SDS字符串缩小时进行数据拷贝并修改len,不释放内存空间,保留原来分配的大小。
二进制安全
传统C字符串只能包含特定编码的数据,而且不能含有空值(‘\0’),否则会当作字符串截断进行处理,导致C字符串只能保存文本数据,而对于二进制数据(图像、音频、视频等)则无能为力,SDS字符串使用len属性判断字符串结束,故能存储任何数据。
3. 源码剖析
- sds定义
1 | /* |
空间预分配,字符串拼接时内存空间不够
- newlen*2 > SDS_MAX_PREALLOC(1MB),则分配1MB内存空间
- newlen*2 < SDS_MAX_PREALLOC, 则分配newlen两倍的内存空间
若长度改变未导致sdshdr头部类型改变,则需要扩展内存空间大小,否则需要重新分配所有内存,将原有数据拷贝,再释放内存空间。
1 | /* |
- 重新分配内存空间,保证没有浪费
1 | /** |