Skip to content

String类型详解

一、String类型的基础特性

String是Redis中最基础、最常用的数据类型,也是其他复杂类型(如List、Hash)的底层构建块。其核心特性包括:

  1. 二进制安全
    支持存储任意二进制数据(如文本、图片、序列化对象、甚至\0字符),因为Redis不依赖字符编码(如UTF-8)来解析内容,仅通过长度判断数据边界。
    例如:SET key "\x00hello\x00" 可以正确存储和获取,不会因\0截断。
  2. 值大小限制
    单个String值的最大容量为512MBredis.conf中的max-string-size参数可调整,但通常不建议修改)。
  3. 原子操作
    所有String命令(如SETINCRAPPEND)都是原子性的,适合分布式场景(如计数器、分布式锁)。
  4. 过期时间
    支持为String设置过期时间(通过EX/PX参数或EXPIRE命令),过期后会被Redis自动删除(遵循惰性删除+定期删除策略)。

二、底层实现:SDS(Simple Dynamic String)

Redis没有使用C语言的原生字符串(以\0结尾的字符数组),而是自定义了SDS(简单动态字符串)结构,解决了C字符串的三大痛点:

  • 无法高效获取长度(需遍历到\0,O(n)时间);
  • 修改时容易内存溢出(如strcat未提前分配足够空间);
  • 不支持二进制安全(\0会被视为字符串结束)。

1. SDS的结构定义

SDS的结构体(以Redis 6.0为例)如下:

c
typedef struct sdshdr {
    int len;      // 已使用的字节数(字符串长度)
    int free;     // 未使用的字节数(剩余空间)
    char buf[];   // 字符数组(存储实际数据)
} sdshdr;
  • len:记录字符串的实际长度,获取长度的时间复杂度为O(1)(对比C字符串的O(n))。
  • free:记录buf中未使用的空间,用于减少内存分配次数(见下文“空间预分配”)。
  • buf:存储字符串的字节数组,末尾会自动添加\0(兼容C语言字符串函数,如printf),但不依赖\0判断长度(由len决定)。

2. SDS vs C字符串的核心优势

特性C字符串SDS
长度获取O(n)(遍历到\0O(1)(直接读len
内存溢出可能(如strcat未扩容)自动扩容(无溢出风险)
二进制安全不支持(\0截断)支持(len判断边界)
修改效率低(频繁分配/回收内存)高(空间预分配+惰性释放)

三、String类型的编码策略

Redis为String类型设计了三种编码方式,根据值的类型和长度自动切换,以优化内存占用和访问效率:

编码类型适用场景内存结构转换触发条件
int整数值(范围:-2^63 ~ 2^63-1直接存储为long long类型(8字节)值超过long long范围,或添加非数字字符(如APPEND "a"
embstr短字符串(默认≤44字节)SDS结构与buf连续存储(分配1次内存)字符串变长超过44字节,或执行修改操作(如APPEND导致扩容)
raw长字符串(>44字节)或非整数SDS结构与buf分开存储(分配2次内存)无(默认长字符串或非整数)

1. int编码

  • 场景:存储整数(如计数器、序号)。
  • 优化:直接用long long类型存储,无需SDS结构的额外开销(len+free共8字节),内存占用最小(仅8字节)。
  • 示例SET counter 100 → 用int编码;INCR counter → 仍为intSET counter "100a" → 转raw编码。

2. embstr编码

  • 场景:存储短字符串(如缓存用户昵称、配置项)。
  • 优化:将SDS的sdshdr结构与buf数组连续分配在同一块内存(仅需1次malloc),减少内存碎片,且缓存命中率更高(连续内存更易被CPU缓存)。
  • 限制embstr只读的,修改时会自动转换为raw编码(因为修改需要重新分配内存,无法保持连续性)。
  • 示例SET name "Alice"(6字节)→ 用embstr编码;APPEND name "Smith"(变为11字节,仍≤44)→ 自动转换为raw编码;APPEND name "..."(超过44字节)→ 转raw编码。

3. raw编码

  • 场景:存储长字符串(如缓存文章内容、二进制文件)或非整数(如JSON字符串)。
  • 结构sdshdr结构与buf数组分开分配(需2次malloc),适合频繁修改的长字符串(修改时仅需调整buf的空间,无需移动sdshdr)。
  • 示例SET article "<html>...</html>"(10KB)→ 用raw编码;APPEND article "<footer>...</footer>" → 自动扩容(见下文“空间预分配”)。

四、String类型的关键机制

1. 空间预分配(减少内存分配次数)

当执行APPENDSETRANGE等修改操作时,SDS会根据当前长度自动分配额外空间,避免频繁调用malloc

  • 若修改后len < 1MBfree分配与len相等的空间(总容量= len × 2)。
    例如:len=10 → 修改后len=20free=20 → 总容量=40。
  • 若修改后len ≥ 1MBfree分配1MB的空间(总容量= len + 1MB)。
    例如:len=2MB → 修改后len=3MBfree=1MB → 总容量=4MB。

2. 惰性空间释放(避免频繁回收内存)

当执行TRIMSET等缩短字符串的操作时,SDS不会立即释放多余的空间,而是将其保留在free中,供后续修改使用:

  • 例如:SET key "hello world"len=11free=0)→ 执行TRIM key 0 4(保留前5字符“hello”)→ len=5free=6(未释放多余的6字节)。
  • 优势:避免频繁调用free(昂贵的系统调用),提升修改效率;若后续需要扩展字符串,可直接使用free空间,无需重新分配。

3. 二进制安全的保证

SDS通过len字段判断字符串的结束,而非\0,因此可以存储任意二进制数据:

  • 例如:SET key "\x00\x01\x02"len=3buf中存储0x000x010x02,获取时会正确返回3字节的数据(对比C字符串会截断为0字节)。

五、String类型的应用场景

String类型的灵活性使其适用于多种场景,以下是常见案例:

  1. 缓存
    存储高频访问的热点数据(如用户信息、商品详情),减少数据库压力。
    示例:SET user:1001 "{\"name\":\"Alice\",\"age\":25}" EX 3600(缓存1小时)。

  2. 计数器
    利用INCR/DECR原子操作实现计数器(如文章阅读量、点赞数、接口调用次数)。
    示例:INCR article:1001:views(阅读量+1)。

  3. 分布式锁
    利用SETNX(SET if Not eXists)命令实现分布式锁(仅当键不存在时设置,保证原子性)。
    示例:SET lock:order:1001 "token" EX 10 NX(获取锁,过期时间10秒)。

  4. 存储二进制数据
    存储图片、音频、视频等二进制文件(需注意大小限制,建议≤10MB,避免占用过多内存)。
    示例:SET image:1001 "\x89PNG\r\n\x1a\n..."(存储PNG图片的二进制内容)。

  5. 配置项存储
    存储系统配置(如开关、阈值),支持快速读取和修改。
    示例:SET config:max_login_attempts 5(最大登录尝试次数)。

六、总结

Redis的String类型是灵活、高效、二进制安全的基础数据类型,其底层通过SDS解决了C字符串的缺陷,通过三种编码策略优化了内存占用和访问效率,通过空间预分配惰性释放提升了修改性能。

关键结论:

  • 短字符串(≤44字节)用embstr编码,内存连续,效率高;
  • 长字符串(>44字节)用raw编码,适合频繁修改;
  • 整数用int编码,内存占用最小;
  • 二进制安全使其能存储任意数据,是缓存、计数器、分布式锁等场景的首选。