Redis 中的 String
是最常用的数据类型之一,它在内部有多种编码方式,用于提升性能和节省内存。下面我们详细介绍一下
Redis 中的 String 对象底层支持 三种编码方式(Encoding),3.2版本之前是39,3.2版本之后才是44:
编码方式 | 说明 | 使用场景 |
---|---|---|
int |
整数 | 可以表示为 long long 的整数 |
embstr |
短字符串(长度 ≤ 44 字节) | 小字符串、高效分配 |
raw |
普通字符串(长度 > 44 字节) | 大字符串 |
int
编码SET age 25
int
long long
范围内)embstr
编码(Embedded String)redisObject
和实际字符串 SDS
分配在一起(一次 malloc),提高分配效率和缓存局部性SET name "Bob"
raw
编码raw
编码redisObject
和字符串内容是分开分配的(两次 malloc)embstr
你可以使用 OBJECT ENCODING key
命令查看 Redis 内部编码:
SET mykey 42
OBJECT ENCODING mykey # 返回 int
SET mykey "hello"
OBJECT ENCODING mykey # 返回 embstr
SET mykey "a very very long string over 44 bytes........"
OBJECT ENCODING mykey # 返回 raw
编码方式 | 特点 | 内存结构 | 触发条件 |
---|---|---|---|
int | 高效存储整数 | 非字符串 | 字符串能转为整数 |
embstr | 快速创建、释放的小字符串 | 连续内存 | 字符串长度 ≤ 44 字节 |
raw | 普通字符串,大字符串 | 分散内存 | 字符串长度 > 44 字节 |
如果你对 SDS(Simple Dynamic String)机制也感兴趣,我可以继续讲讲它是如何扩容、预分配、避免内存碎片的
当然可以!在 Redis 的 raw
编码中,字符串是通过一种特殊的结构 —— SDS(Simple Dynamic String) 来管理的。SDS 的底层结构是 sdshdr
,它比 C 的 char*
更加安全、灵活。下面我们详细看看它的结构和特点
sdshdr
sds
是 Redis 自定义的一种动态字符串类型,它的底层是一个结构体叫 sdshdr
。这个结构不仅存储了字符串的内容,还保存了字符串的长度和分配空间等元信息。
char*
?C 语言的 char*
有这些问题:
strlen()
才能知道字符串长度,时间复杂度 O(n)。而 SDS 就解决了这些问题。
Redis 中的 SDS 实现其实是一个结构体族(根据字符串长度使用不同的结构),我们以常用的 sdshdr8
举例说明:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 当前字符串长度(不含 \0)
uint8_t alloc; // 已分配的空间长度(不含 header 和 \0)
unsigned char flags; // 低 3 位表示是哪种类型的 sdshdr
char buf[]; // 字符串内容(以 \0 结尾)
};
字段 | 含义 |
---|---|
len |
当前字符串实际长度(不包含\0 ) |
alloc |
分配的缓冲区大小(也不含\0 ) |
flags |
记录类型,比如 sdshdr5/8/16/32/64 |
buf[] |
存储实际字符串,最后是\0 结尾 |
size_t sdslen(const sds s);
不需要遍历字符串,效率远超 strlen()
。
如果你执行 sds cat
或追加,SDS 会自动判断是否需要扩容。
预分配机制:比如你追加 “abc” 后,可能会分配更多的内存空间,下次再追加就不用 realloc 了。
为了节省内存,Redis 使用了不同类型的 sdshdr
:
类型 | 最大长度 | 结构体大小 |
---|---|---|
sdshdr5 |
31 字节 | 最小,仅 flags |
sdshdr8 |
255 字节 | 常用小字符串 |
sdshdr16 |
65,535 字节 | 中等字符串 |
sdshdr32 |
4GB | 长字符串 |
sdshdr64 |
超大字符串(稀有) | 特殊场景 |
Redis 会根据字符串的实际长度自动选择最优的 sdshdr 类型。
sds s = sdsnew("hello redis");
printf("长度: %d\n", sdslen(s));
printf("容量: %d\n", sdsavail(s));
输出示例:
长度: 11
容量: 33
说明 alloc
分配了 44 字节,但当前只用了 11。
特性 | 优点 |
---|---|
O(1) 获取长度 | 高效替代 strlen() |
自动扩容 | 更好支持拼接和增长 |
内存安全 | 避免 C 的 buffer overflow |
多结构支持 | 节省内存,适应不同长度的字符串 |
与 C 兼容 | buf[] 末尾是 \0 ,可以与 C 字符串兼容 |
面试中关于 Redis 的 String 类型的提问非常高频,通常围绕数据结构、编码、性能、应用场景等方面。下面我给你整理了一些面试中高频问题 + 答案解析,帮你系统性准备
答:
Redis 的 String 是最基本的数据类型,最大可以存储 512MB 的二进制安全数据。
底层使用的是 SDS(Simple Dynamic String),不是 C 的 char*
,它具备以下优点:
答:
INCR
/DECR
实现)SET key value NX EX 10
)答:
SETNX key value
:只在 key 不存在时设置,只能设置值。SET key value NX
:功能一样,但 SET
命令还支持额外参数,比如 EX
设定过期时间,更推荐使用。 面试建议答:推荐使用 SET key value NX EX 10
原子性地实现分布式锁。
答:
Redis 会根据 value 的内容和大小自动选择编码方式:
编码类型 | 说明 |
---|---|
int | 8 字节整数,节省内存 |
embstr | 小于等于 44 字节的字符串,连续内存块,创建快 |
raw | 长字符串,SDS 实现,支持扩容 |
触发条件:
int
embstr
raw
特性 | embstr | raw |
---|---|---|
内存分配 | 一次性分配对象和数据 | 分别分配 |
适用数据 | 短字符串(≤ 44 字节) | 长字符串(> 44 字节) |
性能 | 创建、释放更快 | 支持动态扩容 |
答:
是的。SDS 在字符串增长时会自动扩容,避免频繁的内存分配。
扩容策略如下:
newlen < 1MB
:多分配相同大小的空间newlen >= 1MB
:多分配 1MB这样可以提升性能,减少 realloc 次数。
答:
能,Redis 的 String 是二进制安全的,可以存储任意内容(图片、音频、序列化对象等),不限制字符编码。
答:
利用 Redis 的 INCR
原子性即可:
INCR user:id
还可以结合业务前缀与时间戳生成订单号等,如:
SET order:20250425 1000
INCR order:20250425
答:
最大为 512MB,超出 Redis 会报错。
答:
核心命令是:
SET lock:job123 "UUID" NX EX 10
加锁成功返回 OK,失败代表锁已被其他客户端占用。
释放锁时使用 Lua 脚本确保删除的是自己加的锁,避免误删:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
11.浮点型在String是用什么表示?
分析:基础知识考查,只有三种编码模式INT只针对于整型,所以浮点型必然是字符串存储。
回答:要将一个浮点数放入字符串对象里面,需要先将这个浮点数转换成字符串值,然后再保存转换所得的字符串值,比如浮点数3.14,对应就变成了"3.14"这个字符串了。所以浮点数在字符串对象里面是用字符串值表示的。
12.Redis字符串底层是String对象,String对象有三种编码方式:INT型、EMBSTR型、RAW型。如果是存在一个整型,可以用long表示的整数就以INT编码存储;如果存字符串,当字符串长度小宇等于一个阈值,使用EMBSTR编码;字符串大于阈值,则用RAW编码。在我用的5.0.5版本中阈值是44。
13.SDS有什么用?
分析:Redis是用C语言写的,SDS可以说是对C字符串的封装,一般对比普通C字符串。可以从计算长度、扩容、缩容、二进制存储这几个场景来描述。
回答:
(1)SDS包含已使用容量字段,O(1)时间快速返回有字符串长度,相比之下,C原生字符串需要O(n)。
(2)有预留空间,在扩容时如果预留空间足够,就不用再重新分配内存,节约性能,缩容时也可以将减少的空间先保留下来,后续可以再使用。
(3)不再以’\0’作为字符串结束判断标注呢,二进制安全,可以很方便地存储一些二进制数据。
如果你需要我总结成思维导图或 PDF 方便复习,请点赞加关注私信我!