Redis/SDS 动态字符串

用显式长度保存包含零字节的字符串

Redis SDS 把字节长度与可用容量放进头部,追加按长度复制,正文允许包含零字节。

7.2.0内存管理核验 2026-10-09

工程问题#

以零字节作为结束标记的 C 字符串无法单独表达任意二进制载荷;重复求长度和频繁扩容也会增加解析、拼接路径的成本。

实际实现#

sdshdr8 等头部区分 len 和 alloc;sdscatlen 先取已用长度,再申请空间,按传入的字节数 memcpy,随后更新长度并补终止符。终止符用于兼容部分 C API,不决定 SDS 的逻辑长度。

处理流程#

  1. 从头部读取当前字节长度
  2. 按新增字节数确保剩余容量,接收可能变化的新地址
  3. 复制指定数量的字节,不用 strlen 推断载荷大小
  4. 更新 len,并在逻辑内容之后写入终止符

源码片段#

固定版本 · 原文摘录
src/sds.c7.2.0 · L479–L493
查看原文件
479/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the480 * end of the specified sds string 's'.481 *482 * After the call, the passed sds string is no longer valid and all the483 * references must be substituted with the new pointer returned by the call. */484sds sdscatlen(sds s, const void *t, size_t len) {485    size_t curlen = sdslen(s);486487    s = sdsMakeRoomFor(s,len);488    if (s == NULL) return NULL;489    memcpy(s+curlen, t, len);490    sdssetlen(s, curlen+len);491    s[curlen+len] = '\0';492    return s;493}
sdscatlen

完整追加函数按 len 复制并显式维护长度;原始注释要求调用后改用返回的新指针。

src/sds.h7.2.0 · L51–L62
查看原文件
51struct __attribute__ ((__packed__)) sdshdr8 {52    uint8_t len; /* used */53    uint8_t alloc; /* excluding the header and null terminator */54    unsigned char flags; /* 3 lsb of type, 5 unused bits */55    char buf[];56};57struct __attribute__ ((__packed__)) sdshdr16 {58    uint16_t len; /* used */59    uint16_t alloc; /* excluding the header and null terminator */60    unsigned char flags; /* 3 lsb of type, 5 unused bits */61    char buf[];62};
sdshdr8 / sdshdr16

两种实际头部都分开存储已用字节数 len 与不含头部、终止符的容量 alloc。

SDS 的长度不是字符数,也不靠最后一个零字节推导。此例不把所有大小的字符串追加笼统宣称为摊还 O(1)。

收益与取舍#

工程分析

带来的收益

  • 显式长度支持包含零字节的二进制内容
  • 取长度无需扫描正文
  • 分离容量与逻辑长度,允许复用预留空间

付出的代价

  • 头部元数据和预留容量增加空间占用
  • 扩容可能改变地址,旧别名不能继续使用
  • 调用方必须检查失败并正确更新句柄,不能把任意 char 指针当成 SDS

可比较的选择

以零结尾的 C 字符串接口普遍,但 strlen 需要扫描,且正文中零字节会截断字符串语义。

指针与长度的只读视图适合不拥有数据的切片,不负责动态扩容或储存生命周期。

版本与适用范围#

对应 Redis 7.2.0 的 SDS。sdshdr5 把短长度放在 flags 中且不记录空闲容量;这里展示的 len/alloc 分离来自其他头类型。二进制安全要求使用带长度的 API,sdscat 等 C 字符串接口仍使用 strlen。

参考来源#

源码、官方文档与原始论文
01
redis · sdshdr8 / sdslen / sdsavail

src/sds.h · sdshdr8 / sdslen / sdsavail

源码7.2.0
02
redis · sdscatlen / _sdsMakeRoomFor

src/sds.c · sdscatlen / _sdsMakeRoomFor

源码7.2.0

相关实现与比较#

4 个相关案例
nginx·HTTP upstream 配置解析release-1.26.3

nginx 解析 upstream 块时,用 ngx_array_t 连续保存服务器条目;每遇到一条 server 指令便追加一个元素。

附源码片段 · 通信与流量
14 个匹配案例