一、前言
我们都知道Redis 是用C语言实现的,但是C 本身并没有内置高级数据结构
如:String(字符串),List(链表),Set(集合),Hash(哈希表) , Zset(有序集合),Stream(流)等
所以,Redis 必须自己从零设计一套高度定制化的数据结构 ,满足其作为高性能内存数据库的独特需求。
二、Redis底层数据结构 →SDS
1、SDS简介
SDS (Simple Dynamic String,简单动态字符串) 是 Redis 自主实现的一种高效、安全、二进制友好的动态字符串结构 ,用于替代 C 语言原生的 char* 字符串。
2、为什么需要SDS
C语言并没有字符串结构,而是通过字符数组来间接的表示字符串,对应的存储结果,如下:

C 语言中的字符串本质是 以 \0(空字符)结尾的字符数组,这种设计存在严重问题:
| 问题 | 说明 |
|---|---|
| 🔴 获取长度需 O(n) | 必须遍历到\0才知道长度(strlen())越长越慢 |
| 🔴 二进制不安全 | 如果数据中间有 \0,会被截断,被误认为字符串结束 (无法存图片、加密数据等) |
| 🔴 缓冲区溢出风险高 | strcat(dest, src) 不检查dest空间是否够大,极易导致内存越界 |
| 🔴 修改效率低 | 无法预分配空间 ,每次拼接都可能触发 realloc(重新分配内存) |
| 🔴 内存浪费或碎片 | 没有统一管理,频繁分配小字符串导致碎片 |
为了解决这些问题,Redis 设计了 SDS,它是一个带有元数据头的结构,支持:
| SDS 优点 |
|---|
✅ 获取长度O(1) : 通过 len 字段直接记录当前字符串长度,无需遍历 |
✅ 二进制安全: 字符串内容由len决定,可包含任意字节(包括 \0),适用于任意二进制数据 |
✅ 杜绝缓冲区溢出: 所有修改操作(如 sdscat)都会先检查 alloc 是否足够,不足则自动扩容后再写入 |
| ✅ 高效修改 + 空间预分配: 采用"预分配冗余空间"策略(<1MB 时翻倍,≥1MB 时 +1MB),大幅减少 realloc 次数 |
✅ 惰性空间释放 + 多种 header 优化内存: 1、缩短字符串时不立即释放内存(仅更新 len),后续可复用 2、根据字符串长度自动选择 sdshdr 5/8/16/32/64,最小化结构体开销 |
3、SDS 的结构

Redis 使用了多种 SDS 类型来优化内存使用,根据字符串长度选择不同 header
c
// Redis源码中的 sds.h 文件
/*
sdshdr8:用于存储长度在 32 ~ 255 字节之间的字符串。
len 和 alloc 使用 uint8_t(1字节),最多表示 255。
当字符串长度 >= 32 且 <= 255 时,Redis 自动选用此结构以节省内存。
*/
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 已使用字节数(字符串实际长度) */
uint8_t alloc; /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_8),高5位保留未用 */
char buf[]; /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};
/*
sdshdr16:用于存储长度在 256 ~ 65535 字节(即 64KB - 1)之间的字符串。
len 和 alloc 使用 uint16_t(2字节),适用于中等大小的字符串。
当字符串长度 >= 256 且 <= 65535 时使用。
*/
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* 已使用字节数(字符串实际长度) */
uint16_t alloc; /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_16),高5位保留未用 */
char buf[]; /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};
/*
sdshdr32:用于存储长度在 65536 ~ 4,294,967,295 字节(约 4GB)之间的字符串。
len 和 alloc 使用 uint32_t(4字节),适用于大字符串(如大JSON、序列化对象等)。
在 32 位或 64 位系统上,只要字符串不超过 4GB,通常优先使用此结构。
*/
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* 已使用字节数(字符串实际长度) */
uint32_t alloc; /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_32),高5位保留未用 */
char buf[]; /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};
/*
sdshdr64:用于存储长度超过 4GB 的超大字符串(极少见,生产环境几乎不用)。
len 和 alloc 使用 uint64_t(8字节),仅在 64 位系统上可能被使用。
当字符串长度 > 2^32 - 1(即 > 4,294,967,295 字节)时启用。
*/
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* 已使用字节数(字符串实际长度) */
uint64_t alloc; /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_64),高5位保留未用 */
char buf[]; /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};
4、SDS 扩容规则(当需要更多空间时)
当 SDS 的当前分配空间(alloc )不足以容纳新增数据时,系统会自动执行扩容操作:
申请一块更大的内存区域,将原有数据 (包括头部元信息和字符串内容)完整复制到新内存,随后释放旧内存,并更新指针使其指向新的地址。
| 条件 | 扩容策略 | 目的 |
|---|---|---|
| 当前字符串长度 < 1MB | 新容量 = 2 × 所需总长度(即至少翻倍) | 减少小字符串频繁 realloc(重新分配内存),提升性能 |
| 当前字符串长度 ≥ 1MB | 新容量 = 所需总长度 + 1MB | 避免大字符串翻倍导致内存浪费 |