Redis 提供的 STRLEN 命令用于返回字符串键当前值的长度,但这里所称的长度并不是我们通常理解的字符个数,而是底层字节数组占用的字节数。第一次接触该命令时,如果直接对中文或 emoji 进行统计,很容易把返回值误读成字符数量。理解 STRLEN 背后的计算逻辑,有助于在缓存设计、大 Key 排查和接口返回长度校验时做出正确判断。

一、STRLEN 命令语法与基础行为
STRLEN 的命令格式非常简单,语法为 STRLEN key。当指定的键不存在时,Redis 返回 0;当键存在但值不是字符串类型时,Redis 会返回一个类型错误。例如对一个列表键执行 STRLEN,会看到 WRONGTYPE Operation against a key holding the wrong kind of value。这一点和 GET、SET 等字符串命令一致,类型检查发生在命令执行的前置阶段。
在 redis-cli 中可以快速验证基础行为。先执行 SET username alice,然后执行 STRLEN username,返回值为 5。这里统计的是五个 ASCII 字符所对应的五个字节。如果键不存在,例如直接执行 STRLEN not_exists_key,得到的结果就是 0,不会报错。需要注意,STRLEN 不区分键本身是否存在,只关注键对应的值对象是否为字符串,因此不存在的键被当作空字符串处理。
127.0.0.1:6379> SET username alice OK 127.0.0.1:6379> STRLEN username (integer) 5 127.0.0.1:6379> STRLEN not_exists_key (integer) 0 127.0.0.1:6379> LPUSH mylist item1 (integer) 1 127.0.0.1:6379> STRLEN mylist (error) WRONGTYPE Operation against a key holding the wrong kind of value
上面的示例中,LPUSH 创建了一个列表键,随后对列表执行 STRLEN 触发了类型错误。这说明 Redis 在键空间找到键对象后,会先检查其类型是否为 OBJ_STRING,只有字符串对象才会进入后续的长度读取流程。在实际业务中,如果多个模块共用 Redis,建议在键命名上区分数据类型,避免类型冲突。
二、SDS 结构如何支撑 O(1) 长度计算
Redis 内部并没有直接使用 C 语言原生的 char* 字符串,而是引入了一种称为 SDS(Simple Dynamic String)的动态字符串结构。原生 C 字符串需要遍历到 \0 才能得到长度,时间复杂度是 O(n),并且无法安全存储二进制数据。SDS 在结构头部维护了一个 len 字段,记录当前已使用的字节数,因此 STRLEN 只需要读取这个字段就能返回结果,时间复杂度稳定为 O(1)。
SDS 的常见结构可以简化为包含 len、alloc、flags 和 buf 几个部分。其中 len 表示字符串当前长度,也就是 STRLEN 返回的值;alloc 表示已分配的总容量,不参与返回值计算。当执行 SET key value 时,Redis 会创建一个 SDS,把 len 设置为值的字节数,并把内容复制到 buf 中。此后执行 STRLEN 时,直接返回 len 的数值,与字符串内容无关。
struct sdshdr {
int len; // 已使用的字节数
int alloc; // 已分配的字节数,不含头部和空字符
unsigned char flags;
char buf[]; // 实际字符串数据
};
这种设计还带来两个额外好处。第一,SDS 可以安全保存任意二进制数据,包括图片、序列化后的 Protobuf 或 JSON 字符串,因为长度由 len 字段决定,而不是依赖末尾空字符。第二,获取长度不会随着值变大而变慢。即使一个字符串占用几十 MB,STRLEN 仍然是 O(1)。不过要注意,尽管 STRLEN 执行很快,但超大字符串在传输、复制和持久化时仍可能带来性能压力,因此不能因为长度读取便宜就无限放大单个值。
三、多字节字符与二进制安全带来的理解差异
Redis 的字符串是二进制安全的,STRLEN 返回的是字节数而非字符数。对于 ASCII 字符,一个字符占一个字节,两者相等;但对于 UTF-8 编码的中文、日文或 emoji,情况就不同了。以常用的汉字“你”为例,在 UTF-8 中编码为三个字节,执行 SET name 你 后再执行 STRLEN name,返回值是 3,而不是 1。如果值是“你好”,返回值是 6。这个行为并非 Redis 缺陷,而是由编码方式和命令定义共同决定的。
127.0.0.1:6379> SET name 你好 OK 127.0.0.1:6379> STRLEN name (integer) 6 127.0.0.1:6379> SET emoji 😀 OK 127.0.0.1:6379> STRLEN emoji (integer) 4
从上面的例子可以看到,emoji 字符通常占用四个字节,所以 STRLEN 返回 4。如果业务希望获得用户看到的字符数,需要区分计算场景。一种做法是在应用层读取值后使用语言内置的字符计数函数,例如 Python 的 len(str) 对 Unicode 字符串返回字符数,而 Java 的 String.length() 返回 UTF-16 代码单元数,也不一定完全等于视觉字符数。另一种做法是在写入 Redis 前将字符串转换为固定编码,并自行维护一个字符长度字段,但这样会增加一致性成本。
另一个容易混淆的场景是 Redis 对 UTF-8 字符串做范围操作。GETRANGE 使用字节偏移量,SETRANGE 也按字节覆盖。如果直接按字符索引截取中文,可能会把某一个汉字的多个字节切开,造成乱码。因此使用 STRLEN 和其他字节级命令时,最好统一按字节语义理解,避免与前端按字符展示的逻辑混用。对于需要精确字符长度的功能,例如昵称长度限制,建议在应用层完成校验,而不是依赖 Redis 的 STRLEN。
四、性能对比与实战建议
STRLEN 与 GET 相比,省去了把整个值从 Redis 服务端传送到客户端的网络开销。如果只是想确认一个字符串是否超过阈值,或者想统计一批键的长度分布,直接调用 STRLEN 会比 GET 后取长度更高效。尤其是值较大的情况下,STRLEN 不需要读取值内容本身,响应包体非常小。假设一个 Key 的值为 10 MB,GET 会占用大量网络带宽和时间,而 STRLEN 只返回一个整数。
在排查大 Key 时,可以借助 redis-cli --bigkeys 或自定义脚本遍历键,并通过 STRLEN 快速找出字符串类型的超大键。不过需要注意,redis-cli --bigkeys 对于字符串类型内部使用的就是 STRLEN,它不会读取完整值,因此扫描效率可以接受。还有一点,STRLEN 的时间复杂度虽然为 O(1),但在集群或大量键场景下,逐键执行 STRLEN 仍然需要一次网络往返,此时更好的方式是使用 Lua 脚本或 pipeline 批量处理,减少 RTT。
redis-cli --bigkeys
具体编码时,如果业务只关心长度,优先选择 STRLEN。例如做缓存预热检查时,判断某个键是否为空可以用 STRLEN key 是否等于 0,而不是 GET key 后再判断。对不存在的键,两种方式结果一致,但 STRLEN 没有值传输。对于需要同时返回长度和部分内容的场景,可以考虑分两步:先 STRLEN 判断大小,再决定是否用 GETRANGE 取前若干字节,这样可以避免一次性拉取超大值导致客户端内存压力。
最后,STRLEN 与 SETRANGE、APPEND 配合时要注意长度会实时变化。例如执行 APPEND key suffix 后,len 会自动增加,后续 STRLEN 返回新的字节数。如果值中包含空字符 \0,STRLEN 依然按照 len 字段统计,不会像 C 字符串那样提前截断。这也是二进制安全的一个重要体现。实际项目中,只要理解 Redis 字符串以字节为基本单位,大部分长度统计问题都能自然消解。
Redis STRLEN字符串长度二进制安全修改时间:2026-09-26 22:42:10