阿里面试:redis 为什么把简单的字符串设计成 SDS?

Redis中的String数据结构采用SDS而不是C语言的字符串,因为SDS具有效率高、避免数据溢出、内存重分配策略以及支持数据多样性等优势。SDS结构包含len、free和buf属性,提供O(1)的字符串长度获取,且在字符串增长和缩短时通过空间预分配和惰性空间释放策略优化内存操作。

面试官:了解redis的String数据结构底层实现嘛?
铁子:当然知道,是基于SDS实现的

面试官:redis是用C语言开发的,那为啥不直接用C的字符串,还单独设计SDS这样的结构呢?

铁子:·····

其实看得出面试官是想看看,铁子是只停留在redis的使用层面,还是对底层数据结构有过更深入的研究,面试嘛都爱这样问大家都懂得。

我们知道redis是用C写的,但它却没有完全直接使用C的字符串,而是自己又重新构建了一个叫简单动态字符串SDS(simple dynamic string)的抽象类型。

redis也支持使用C语言的传统字符串,只不过会用在一些不需要对字符串修改的地方,比如静态的字符输出。

而我们开发中使用redis,往往会经常性的修改字符串的值,这个时候就会用SDS来表示字符串的值了。有一点值得注意:在redis数据库中,key-value键值对含有字符串值的,都是由SDS来实现的。

比如:在redis执行一个最简单的set命令,这时redis会新建一个键值对。

127.0.0.1:6379> set xiaofu “程序员内点事”

此时键值对的key和value都是一个字符串对象,而对象的底层实现分别是两个保存着字符串“xiaofu”和“程序员内点事”的SDS结构。

再比如:我向一个列表中压入数据,redis 又会新建一个键值对。

127.0.0.1:6379> lpush xiaofu “程序员内点事” “程序员小富”

这时候键值对的键和上边一样,还是一个由SDS实现的字符串对象,键值对的值是一个包含两个字符串对象的列表对象了,而这两个对象的底层也是由SDS实现。

SDS结构

一个SDS值的数据结构,主要由len、free、buf[]这三个属性组成。</

Redis字符串计为 **SDS(Simple Dynamic String)** 而不是直接使用 C 语言的原生字符串(`char*`),主要是为了解决原生字符串的局限性,并优化性能、安全性和功能扩展性。以下是 SDS 的核心计原因和优势: --- ### **1. 解决 C 字符串的固有缺陷** C 语言的字符串以 `\0` 结尾,**无法直接存储二进制数据**(如包含 `\0` 的图片、序列化对象等),且长度计算需遍历整个字符串(`strlen` 是 O(n) 操作)。 **SDS 的改进**: - **独立存储长度**:SDS 结构中包含 `len` 字段,记录字符串的字节长度,**O(1) 时间获取长度**。 - **支持二进制安全**:SDS 不依赖 `\0` 结尾,可以存储任意二进制数据(包括 `\0`)。 ```c // SDS 结构示例(Redis 5.0+) struct sdshdr { uint8_t len; // 已用长度 uint8_t alloc; // 分配的总空间(不含头和null终止符) char buf[]; // 实际字符串数据(可能包含\0) }; ``` --- ### **2. 动态扩容与内存优化** C 字符串每次修改(如拼接)可能需要重新分配内存并复制数据,效率低下。 **SDS 的改进**: - **惰性空间分配**:SDS 扩容时遵循**空间预分配策略**(如增长后 `alloc` 大于 `len`),减少频繁内存分配。 - 例如:`sdsMakeRoomFor` 会预分配额外空间(如 `len < 1MB` 时翻倍,否则多分配 1MB)。 - **避免内存泄漏**:SDS 的内存管理由 Redis 统一处理,与对象系统集成。 --- ### **3. 减少内存重分配次数** C 字符串在修改时(如缩短)不会自动释放内存,可能导致内存浪费。 **SDS 的改进**: - **惰性释放**:缩短字符串时,SDS 不会立即释放内存,而是通过 `alloc` 字段记录剩余空间,供后续操作复用。 - **安全释放**:通过 `sdsfree` 统一释放内存,避免手动管理错误。 --- ### **4. 兼容 C 字符串函数** SDS 仍以 `\0` 结尾(`buf[len] = '\0'`),**兼容 C 字符串函数**(如 `printf`、`strcpy`),无需额外转换。 **优势**: - 可以直接将 SDS 传递给需要 `char*` 的库函数(如文件操作、网络传输)。 - 避免在 C 生态中频繁转换数据格式。 --- ### **5. 丰富的字符串操作 API** RedisSDS 提供了高效的 API,覆盖常见操作: - **拼接**:`sdscat`、`sdscatsds`(O(n) 时间,但通过预分配优化)。 - **截取**:`sdsrange`(支持范围操作)。 - **复制/比较**:`sdsdup`、`sdscmp`。 - **编码转换**:如 `sdsEncodeLength` 处理长度字段。 **示例**: ```c sds str = sdsnew("Hello"); str = sdscat(str, " Redis!"); // 拼接字符串 printf("%s\n", str); // 输出: "Hello Redis!" sdsfree(str); // 释放内存 ``` --- ### **6. 与 Redis 对象系统集成** Redis 的所有数据类型(如 String、Hash、List)底层都依赖 SDS 或类似结构: - **统一管理**:SDS 的内存分配/释放由 Redis 的 `zmalloc` 统一处理,便于追踪内存使用。 - **编码优化**:Redis 根据字符串长度选择不同的存储编码(如 `EMBSTR` 或 `RAW`),SDS 的 `len` 和 `alloc` 字段支持这种优化。 --- ### **7. 性能对比示例** | **操作** | **C 字符串** | **SDS** | |------------------------|---------------------------------------|----------------------------------| | 获取长度 | `strlen(str)`(O(n)) | `sdslen(str)`(O(1)) | | 拼接字符串 | 可能多次重分配内存 | 预分配空间,减少重分配次数 | | 存储二进制数据 | 不可行(依赖 `\0`) | 支持任意二进制数据 | | 内存释放 | 需手动管理 | 通过 `sdsfree` 统一释放 | --- ### **总结:为什么 Redis 选择 SDS?** 1. **二进制安全**:支持任意数据(包括 `\0`)。 2. **高效操作**:O(1) 时间获取长度,预分配减少内存重分配。 3. **兼容性**:保留 `\0` 结尾,兼容 C 生态。 4. **内存管理**:与 Redis 对象系统集成,避免泄漏。 5. **功能丰富**:提供安全的字符串操作 API。 SDS计使 Redis 在处理字符串时既能保持高性能,又能避免 C 原生字符串的诸多问题,是 Redis 高性能的关键基础组件之一。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyufeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值