第2章 简单动态字符串

从sdshdr的len、free和buf理解常数时间取长、二进制安全、空间预分配与惰性释放 覆盖5个正式节点,并以Redis 3.0源码、故障和恢复对账验收。

学习目标

  • 能画出 sdshdr 的 len、free、buf 三字段布局
  • 能解释二进制安全与 O(1) 取长的实现
  • 能分析预分配与惰性释放的内存权衡

为什么从“SDS字节与扩容轨迹台”开始

第2章 简单动态字符串的核心任务是从sdshdr的len、free和buf理解常数时间取长、二进制安全、空间预分配与惰性释放。命令返回值只暴露外层合同;实现解释还必须连接内存结构、写入与读取路径、事件顺序以及失败后的旧状态回收。SDS字节与扩容轨迹台把这些关系放进同一条可复位轨迹。

先写预测:当写入长度由“跨预留空间”进入“含零字节”时,哪个可观察状态最先变化?再规定什么结果会推翻当前解释。交互中的分数只表达透明因果方向,不冒充真实Redis测量。

来源、版次与独立重写边界

黄健宏作者读者服务页确认正式出版新版以Redis 3.0为源码基线,列出4部分、24章及完整小节,并链接Redis 3.0中文注释源码。本课程据此映射24个正式单元、145个目录节点,另设学习地图和总复习;未取得出版正文授权,目录只界定范围,不宣称复现原书正文。

本章字段与控制流由Redis官方3.0源码:sds.c和作者注释源码交叉核对;Redis当前持久化文档只用于辨认版本差异。中文解释、图示、交互、实验与答案均为独立教学重写,不把目录页或代码仓库许可证误报为原书许可证。

本章术语与源码合同

第2章 简单动态字符串必须守住“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”。观察同时记录SDS字节与扩容轨迹台一致率与空间状态分叉风险;只有结构快照、函数入口、运行结果和故障反例互相一致,才接受实现结论。

⚡ SDS 字节级内存布局实验台
struct sdshdr 内存布局(32 位 header + buf)len 记录已用 · free 记录剩余 · buf 承载数据与结尾 \0;点下方按钮观察追加、扩容与惰性释放头部len = 500010203free = 1104050607buf[]数据(5 字节)520651642693734预分配(11 字节)·5·6·7·8·9·10·11·12·13·14·15\0\016内容 ASCII:[ R e d i s ]
len=5free=11capacity=16O(1) 取长:直接读 len,无需扫描
二进制安全:len 记录长度,内容里的任意字节都不会被截断
"Redis" —— SDS 按 len 读出完整内容,即使字节序列里出现 \0 也原样保留(图片、序列化对象均可安全存储)。
C 字符串 以 \0 为终止符:遇到第一个 \0 就停止,二进制数据会被拦腰截断——这是 SDS 存在的根本原因之一。
操作日志
  1. 1.初始状态:sdshdr { len=5, free=11 },buf 分配 16 字节 + 结尾 \0。

作者目录逐项深读

SDS的定义

四级证据 1/5。 sdshdr在buf前保存len与free,buf仍以零字节结尾,因此可兼容部分C API又能常数时间取长。本节点用“SDS的定义”作观察点,执行rg 'sdsMakeRoomFor|sdslen|sdsRemoveFreeSpace' src/sds.c或等价源码探针,保存版本、输入、前后状态和能推翻解释的反例。

验证SDS的定义时,先预测写入长度从“跨预留空间”切到“含零字节”会改变哪个字段、偏移、文件或消息;再固定空间状态做一次对照。若故障注入没有破坏“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”,就撤回当前解释而不是补写故事。

SDS与C字符串的区别

四级证据 2/5。 SDS按len处理二进制数据并在写入前检查容量,避免strlen线性扫描、缓冲区溢出与嵌入零截断。本节点用“SDS与C字符串的区别”作观察点,执行rg 'sdsMakeRoomFor|sdslen|sdsRemoveFreeSpace' src/sds.c或等价源码探针,保存版本、输入、前后状态和能推翻解释的反例。

验证SDS与C字符串的区别时,先预测写入长度从“跨预留空间”切到“含零字节”会改变哪个字段、偏移、文件或消息;再固定空间状态做一次对照。若故障注入没有破坏“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”,就撤回当前解释而不是补写故事。

SDS API

四级证据 3/5。 sdsMakeRoomFor先保证空间,拼接更新len/free,sdsRemoveFreeSpace可主动收缩预留区。本节点用“SDS API”作观察点,执行rg 'sdsMakeRoomFor|sdslen|sdsRemoveFreeSpace' src/sds.c或等价源码探针,保存版本、输入、前后状态和能推翻解释的反例。

验证SDS API时,先预测写入长度从“跨预留空间”切到“含零字节”会改变哪个字段、偏移、文件或消息;再固定空间状态做一次对照。若故障注入没有破坏“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”,就撤回当前解释而不是补写故事。

重点回顾

四级证据 4/5。 第2章 简单动态字符串的回顾要重新证明“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”,并用同一输入比较结构前态、变更轨迹、故障首错与恢复后态

验证重点回顾时,先预测写入长度从“跨预留空间”切到“含零字节”会改变哪个字段、偏移、文件或消息;再固定空间状态做一次对照。若故障注入没有破坏“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”,就撤回当前解释而不是补写故事。

参考资料

四级证据 5/5。 本章目录范围由作者页面确认,字段和控制流回到Redis 3.0的sds.c与黄健宏注释源码核验;新版资料只承担差异说明

验证参考资料时,先预测写入长度从“跨预留空间”切到“含零字节”会改变哪个字段、偏移、文件或消息;再固定空间状态做一次对照。若故障注入没有破坏“字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断”,就撤回当前解释而不是补写故事。

最小源码与运行切片

+git clone --branch 3.0 --depth 1 https://github.com/redis/redis.git redis-3.0
+cd redis-3.0
+rg 'sdsMakeRoomFor|sdslen|sdsRemoveFreeSpace' src/sds.c

该切片固定Redis 3.0分支、编译器、配置、数据集和命令序列;先保存结构或函数位置,再运行隔离实例。任何持久化损坏、断线、故障转移、大键或高流量实验都必须使用临时数据并规定CPU、内存、磁盘、延迟和停止上限。

unit: rdi-02-simple-dynamic-string
source_file: sds.c
axis_a: "写入长度"
axis_b: "空间状态"
fault: "按C字符串的strlen和终止零推断SDS长度,截断二进制载荷"
invariant: "字符串长度、终止字节和可用空间始终一致,扩容不会溢出,二进制数据不被截断"
replay: same_version_same_input

三个必须主动触发的误区

误区 1

现象 → 把 SDS 当 C 字符串用 原因 → 遇 \0 截断破坏二进制数据 修法 → 用 len 字段语义处理任意字节

误区 2

现象 → 频繁 append 不管扩容 原因 → 每次重分配拖慢写入 修法 → 预估容量一次分配,或定期整理

误区 3

现象 → 忽视内存碎片 原因 → 长短交替造成空间闲置 修法 → 监控内存碎片率,必要时重启整理

小结

  • SDS 是 Redis 字符串的默认实现
  • 预分配与惰性释放减少重分配
  • O(1) 取长度靠 len 字段
  • 二进制安全可存任意字节
  • 兼容部分 C 字符串函数

练习、答案与节点验证

练习

问题 1: SDS 为什么能 O(1) 取长度且二进制安全?

问题 2: SDS 的预分配会带来什么副作用?

问题 3: 手写一个 SDS 追加函数,要求先检查 free 再决定是否扩容。(独立实现)

术语复核与本章回顾

完成第2章 简单动态字符串意味着能从sds.c解释从sdshdr的len、free和buf理解常数时间取长、二进制安全、空间预分配与惰性释放,能运行章专属状态实验,能制造反例并在复位后证明旧状态没有残留。

资料与写作方式声明

本章以黄健宏《Redis设计与实现》(机械工业出版社)权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…