Redis高频面试题——String对象

Redis 中的 String 是最常用的数据类型之一,它在内部有多种编码方式,用于提升性能和节省内存。下面我们详细介绍一下


Redis String 对象的编码类型

Redis 中的 String 对象底层支持 三种编码方式(Encoding)3.2版本之前是39,3.2版本之后才是44

编码方式 说明 使用场景
int 整数 可以表示为 long long 的整数
embstr 短字符串(长度 ≤ 44 字节) 小字符串、高效分配
raw 普通字符串(长度 > 44 字节) 大字符串

逐个介绍

1. int 编码

  • 存储内容是一个整数,比如:SET age 25
  • Redis 会将该 String 对象编码为 int
  • 优点:节省内存,操作效率高(无需分配字符串结构)
✅ 使用条件:
  • 值能被解析为整数(long long 范围内)

2. embstr 编码(Embedded String)

  • 用于存储长度小于等于 44 字节的字符串
  • Redis 会将 redisObject 和实际字符串 SDS 分配在一起(一次 malloc),提高分配效率和缓存局部性
优点:
  • 创建快,释放快,内存连续
  • 常用于小字符串,如:SET name "Bob"

3. raw 编码

  • 超过 44 字节的长字符串使用 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 就解决了这些问题。


sdshdr 的常见结构

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结尾

优点分析

✅ 获取长度是 O(1)

size_t sdslen(const sds s);

不需要遍历字符串,效率远超 strlen()

✅ 自动扩容

如果你执行 sds cat 或追加,SDS 会自动判断是否需要扩容。

✅ 减少内存碎片

预分配机制:比如你追加 “abc” 后,可能会分配更多的内存空间,下次再追加就不用 realloc 了


多种 sdshdr 类型

为了节省内存,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)


1️⃣ Redis 的 String 是什么?底层是怎么实现的?

答:
Redis 的 String 是最基本的数据类型,最大可以存储 512MB 的二进制安全数据。
底层使用的是 SDS(Simple Dynamic String),不是 C 的 char*,它具备以下优点:

  • 记录字符串长度(O(1) 获取)
  • 自动扩容、预分配空间
  • 安全:防止缓冲区溢出
  • 多种类型(sdshdr5、sdshdr8、16、32、64)节省内存

2️⃣ String 的常见应用场景有哪些?

答:

  • 缓存对象(JSON、序列化后的对象)
  • 计数器(用 INCR/DECR 实现)
  • 分布式锁(SET key value NX EX 10
  • 限流器(结合 Lua 脚本 + INCR)
  • 存储配置信息、Token、验证码等

3️⃣ Redis SETNX 和 SET NX 有什么区别?

答:

  • SETNX key value:只在 key 不存在时设置,只能设置值
  • SET key value NX:功能一样,但 SET 命令还支持额外参数,比如 EX 设定过期时间,更推荐使用。

面试建议答:推荐使用 SET key value NX EX 10 原子性地实现分布式锁。


4️⃣ String 的编码方式有哪些?什么时候会用?

答:

Redis 会根据 value 的内容和大小自动选择编码方式:

编码类型 说明
int 8 字节整数,节省内存
embstr 小于等于 44 字节的字符串,连续内存块,创建快
raw 长字符串,SDS 实现,支持扩容

触发条件:

  • 字符串是整数 ➜ int
  • 字符串 ≤ 44 字节 ➜ embstr
  • 字符串 > 44 字节 ➜ raw

5️⃣ embstr 和 raw 区别?

特性 embstr raw
内存分配 一次性分配对象和数据 分别分配
适用数据 短字符串(≤ 44 字节) 长字符串(> 44 字节)
性能 创建、释放更快 支持动态扩容

6️⃣ 你了解 SDS 的预分配机制吗?

答:
是的。SDS 在字符串增长时会自动扩容,避免频繁的内存分配。

扩容策略如下:

  • newlen < 1MB:多分配相同大小的空间
  • newlen >= 1MB:多分配 1MB

这样可以提升性能,减少 realloc 次数。


7️⃣ String 类型能存二进制数据吗?

答:
能,Redis 的 String 是二进制安全的,可以存储任意内容(图片、音频、序列化对象等),不限制字符编码。


8️⃣ 如何实现一个全局唯一 ID 生成器?

答:

利用 Redis 的 INCR 原子性即可:

INCR user:id

还可以结合业务前缀与时间戳生成订单号等,如:

SET order:20250425 1000
INCR order:20250425

9️⃣ String 类型最大能存多少数据?

答:
最大为 512MB,超出 Redis 会报错。


1️⃣0️⃣ 如何实现分布式锁?

答:
核心命令是:

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 方便复习,请点赞加关注私信我!

你可能感兴趣的:(Redis,Java找工作,redis,数据库,缓存)