1. 引言
Redis没有直接使用C语言的标准字符串(以\0
结尾的字符数组),而是自定义了SDS(Simple Dynamic String)。SDS是Redis的基础数据结构之一,广泛用于键值存储、命令参数等场景。本篇将深入剖析SDS的实现原理、优势以及源码细节。
2. 为什么不用C标准字符串?
C字符串存在以下问题:
- 缓冲区溢出:
strcat
等操作可能越界。 - 长度计算:需要遍历到
\0
,时间复杂度O(n)。 - 内存管理:频繁的拼接和释放效率低下。
SDS通过额外的元数据和优化策略,解决了这些问题,成为Redis高性能的基石。
3. SDS的结构体定义
SDS的定义在src/sds.h
和src/sds.c
中。Redis 3.2之后引入了多种SDS类型以节省内存,但核心思想一致。我们以最基本的SDS结构为例:
代码片段(sds.h
):
typedef char *sds;struct sdshdr {unsigned int len; // 已使用长度unsigned int free; // 未使用长度char buf[]; // 实际存储数据的柔性数组
};
len
:记录字符串的实际长度,避免遍历。free
:记录剩余可用空间,支持动态扩展。buf
:存储字符串内容,紧跟结构体。
硬核点:SDS的内存布局是连续的,sds
指针直接指向buf
,而通过指针偏移可以访问sdshdr
。
Mermaid内存布局图:
classDiagramclass SDS {-len: uint-free: uint-buf: char[]}note for SDS "sds指针指向buf起始地址"
4. SDS的核心操作解析
4.1 创建SDS(sdsnew()
)
代码片段(sds.c
):
sds sdsnew(const char *init) {size_t initlen = (init == NULL) ? 0 : strlen(init);return sdsnewlen(init, initlen);
}sds sdsnewlen(const void *init, size_t initlen) {struct sdshdr *sh;size_t alloc = initlen;sh = zmalloc(sizeof(struct sdshdr) + alloc + 1); // +1 为\0sh->len = initlen;sh->free = 0;if (init) memcpy(sh->buf, init, initlen);sh->buf[initlen] = '\0';return (char*)sh->buf;
}
硬核解析:
zmalloc()
:Redis自定义的内存分配器。- 内存分配包括
sdshdr
头部和buf
(加1字节存放\0
以兼容C函数)。 - 返回值是
buf
的地址,隐藏了头部信息。
4.2 拼接SDS(sdscat()
)
代码片段(sds.c
):
sds sdscat(sds s, const char *t) {return sdscatlen(s, t, strlen(t));
}sds sdscatlen