Redis 的 SDS:像橡皮筋笔记本一样好用的字符串
目录
从传统字符串的痛点说起
两种字符串的本质区别
SDS 的智能设计:让笔记本更懂你
神奇的 "双标签" 系统
空间预分配:提前留好备用纸
惰性空间释放:不撕纸的智慧
二进制安全:完整记录所有符号
兼容旧工具:隐藏的小标记
揭开 SDS 的结构面纱
实际例子:SET msg "hello" 背后的结构
从传统字符串的痛点说起
想象你用两种笔记本记账:
- 传统 C 字符串就像明信片,写完不能改,想加内容必须换张新的重写;查字数得逐个清点;遇到句号就停笔,后面内容全丢。
- 而 Redis 发明的 SDS(简单动态字符串)就像带橡皮筋的智能笔记本,能自动伸缩、随时查字数、完整记录所有内容 —— 这就是 Redis 为什么把 SDS 作为默认字符串表示的原因。
两种字符串的本质区别
问题场景 | 明信片(C 字符串) | 橡皮筋笔记本(SDS) |
改内容 | 必须换张新的重写 | 直接改,不够长自动拉长 |
查长度 | 逐个字数,费时间 | 封面直接标着数字 |
写太多 | 内容会写到别人的纸上(缓冲区溢出) | 自动加纸,绝不越界 |
特殊符号 | 遇到句号就停笔 | 完整记录所有内容 |
SDS 的智能设计:让笔记本更懂你
神奇的 "双标签" 系统
SDS 笔记本封面有两个关键标签:
- len(已写页数):直接标着内容长度,查字数不用逐页翻(O (1) 复杂度)
- free(空白页数):记录还能写多少字,避免空间不足的尴尬
这对标签就像笔记本的智能导航系统,让 Redis 随时知道内容有多长、还能加多少内容。
空间预分配:提前留好备用纸
创建 SDS 时会自动预留空白页:
- 记短内容(如 "msg"3 个字符)时,按 1:1 预留空白(len=3,free=3)
- 就像写日记时多留几页,下次加内容不用换本子
- 内容超过 1MB 时,固定留 1MB 空白(比如 20MB 内容只留 1MB 空白)
- 就像搬家时小箱子留同等空间,集装箱则固定留一个小箱子空间,避免浪费
这种设计通过sdsMakeRoomFor函数实现,能大幅减少换本子(内存分配)的次数。
惰性空间释放:不撕纸的智慧
缩短内容时,SDS 不会马上撕掉多余的纸:
- 把 "msg" 改成 "m" 后,len 变成 1,原来的空间变成空白(free=5)
- 就像擦掉写错的字但不撕页,空白留着下次用
- 需要时可调用sdsRemoveFreeSpace函数手动释放空白空间
这种 "不浪费空白" 的设计,避免了频繁调整本子厚度(内存重分配)的麻烦。
二进制安全:完整记录所有符号
C 字符串遇到句号(\0)就停笔,而 SDS 靠 len 判断结束:
- 记 "m. sg" 时,C 只会记到 "m",SDS 则完整保存全部内容
- 就像录音笔会记录整首歌,包括中间的停顿
- 不管内容里有什么特殊符号,都能原样保存和读取
兼容旧工具:隐藏的小标记
虽然 SDS 靠 len 判断结束,但每个本子最后还是会偷偷画个小三角(\0):
- 这个标记不占 len 计数,也不影响内容
- 却能兼容传统 C 语言的阅读工具
- 就像笔记本最后留一页空白做标记,方便用旧书签查找
揭开 SDS 的结构面纱
这些智能设计在代码中是这样实现的:
struct sdshdr {int len; // 已写内容长度(已写页数)int free; // 空白长度(空白页数)char buf[]; // 内容数组(内页纸)};
实际例子:SET msg "hello" 背后的结构
执行SET msg "hello"后,会创建两个 SDS 对象:
- 键 "msg" 的 SDS 结构:
- len=3("msg" 有 3 个字符)
- free=3(按 1:1 预分配空白)
- buf=['m','s','g','\0'](最后是隐藏标记)
- 值 "hello" 的 SDS 结构:
- len=5("hello" 有 5 个字符)
- free=5(预分配 5 个空白)
- buf=['h','e','l','l','o','\0']
注意:空白页(free)为 0 时,表示这个 SDS 没有预留任何空白空间,下次修改必须分配新空间。
SDS 就像一本懂你习惯的智能笔记本,用贴心的设计解决了传统笔记本的各种麻烦。它通过 "提前备货" 和 "不浪费空白" 的空间管理策略,加上 "完整记录" 和 "兼容旧工具" 的实用设计,让 Redis 处理数据时又快又安全 —— 这就是为什么 Redis 里几乎所有需要修改的字符串,都用 SDS 来实现的原因~