前言
strlen()返回字符串的字节数。注意,手册里写的是字节数,不是字符数——这是关于它最需要记住、也最容易出错的一点。
最常见的症状是:一段中文昵称,肉眼数着是 4 个字,strlen()却报 12。「长度校验怎么总是提前触发」这类问题,根子几乎都在这里。原因是 UTF-8 编码下一个常用汉字占 3 个字节,strlen()老老实实地把 12 个字节数了出来。
第二个常见误解方向正好相反:有人担心strlen()会遍历整个字符串所以「很慢」,于是写出一堆手动缓存长度的代码。这也是错的。PHP 的字符串是「长度 + 字节内容」的结构(所以它是二进制安全的),长度就存在结构体里,strlen()不需要扫描内容,它的开销与字符串长度无关。
本文讲清strlen()的签名与返回值、字节与字符的区别、如何正确截断中文字符串、以及在使用长度函数时该选哪一个。
一、签名与返回值
以 PHP 官方手册为准:
strlen(string $string): int
| 参数 | 任意字符串,二进制安全,内容里含\0也不会被截断 |
| 失败 | 正常情况不会失败,返回值恒为 int(不会返回false) |
| 版本提示 | PHP 8.1 起,向非可空参数传null会触发废弃提示 |
| 非字符串入参 | PHP 8 起传入数组会抛TypeError |
字节与字符的差异,用一张表最直观:
| 字符串 | strlen() | mb_strlen($s, 'UTF-8') |
|---|
<?php
// 适用于 PHP 8.0+
var_dump(strlen('abc')); // int(3)
var_dump(strlen('你好')); // int(6) 两个汉字,每个 3 字节
var_dump(strlen('中文abc')); // int(9) 6 + 3
var_dump(strlen('')); // int(0)
// PHP 7.0 起支持 \u{} 转义,可以直接写出码位
$emoji = "\u{1F600}";
var_dump(strlen($emoji)); // int(4) UTF-8 下该码位占 4 字节
var_dump(mb_strlen($emoji, 'UTF-8')); // int(1) 但只算一个字符
mb_strlen()属于 mbstring 扩展(mbstring是常用扩展,但不保证一定编译进来,可以用extension_loaded('mbstring')检查)。它接收第二个参数指定编码,建议永远显式传'UTF-8',而不是依赖mb_internal_encoding()的当前设置——那个设置可能被框架、被 ini 文件、被别的代码改掉,一旦不是 UTF-8,mb_strlen()的结果会跟着变,这类 bug 极难复现。
二、什么时候用 strlen,什么时候用 mb_strlen
判断依据只有一个:约束是「字节」还是「字符」。
| 校验昵称、标题的字数上限 | mb_strlen($s, 'UTF-8') | 用户感知的是「几个字」 |
对齐 MySQLvarchar(20)的长度 | mb_strlen($s, 'UTF-8') | varchar的长度按字符计 |
对齐varbinary/blob列 | strlen($s) | 二进制列按字节计 |
| 校验写入文件、发送到 socket 的数据量 | strlen($s) | 传输层按字节算 |
| 缓存键长度、队列消息体大小限制 | strlen($s) | 通常按字节限制 |
| 判断字符串是否为空 | $s === '' | 最直接,不涉及函数调用 |
一句话总结:面向人的长度用mb_strlen(),面向机器(存储、传输、协议)的长度用strlen()。把这两条对齐清楚,「长度校验总是差几倍」的问题就不会再出现。
<?php
// 适用于 PHP 8.0+
function validateNickname(string $nickname, int $maxChars = 10): bool
{
// 业务约束是「不超过 10 个字」,所以必须按字符数判断
return mb_strlen($nickname, 'UTF-8') <= $maxChars;
}
var_dump(validateNickname('你好世界')); // bool(true) 4 个字
var_dump(validateNickname('你好世界你好世界你好世界')); // bool(false) 12 个字,超过上限 10
不要写成strlen($nickname) <= 10——那样一个汉字会被算成 3 个,用户打 4 个字就被判定超长。
三、按长度截断中文字符串
substr()是按字节切的,用它截断 UTF-8 字符串会切出「半个汉字」,产生一段无效字节序列。
<?php
// 适用于 PHP 8.0+
$s = '你好世界';
// 按字节切:长度看起来没错,内容却是坏的
$cut = substr($s, 0, 4);
var_dump(strlen($cut)); // int(4)
var_dump(mb_check_encoding($cut, 'UTF-8')); // bool(false) 这 4 字节不是合法 UTF-8
// 按字符切:正确
$ok = mb_substr($s, 0, 2, 'UTF-8');
var_dump($ok); // string(6) "你好"
var_dump(strlen($ok)); // int(6) 2 个字符,6 个字节
// 按字节切但保证不切坏字符:mb_strcut()
$byteCut = mb_strcut($s, 0, 4, 'UTF-8');
var_dump($byteCut); // string(3) "你"
var_dump(mb_check_encoding($byteCut, 'UTF-8')); // bool(true)
三个函数的区别值得单独记一下:
substr($s, $start, $length) | 字节 | 可能切出半个字符,产生无效编码 |
mb_substr($s, $start, $length, 'UTF-8') | 字符 | 按字符边界切割,结果始终合法 |
mb_strcut($s, $start, $length, 'UTF-8') | 字节 | 按字节算长度,但不会切断字符 |
用法上:「最多显示 20 个字」用mb_substr();「这个字段最多 30 字节」用mb_strcut()。两者都能保证结果仍是合法的 UTF-8,不会给后续的json_encode()制造麻烦——json_encode()遇到无效 UTF-8 会直接返回false,这是乱码截断之后最常见的连锁事故。
四、长度函数的选型
PHP 里数长度的函数不止一个,各有明确的适用面:
strlen() | 字节 | 核心 | 二进制数据、协议、存储限制 |
mb_strlen() | 字符(按编码) | mbstring | 面向用户的字数校验 |
iconv_strlen() | 字符(按编码) | iconv | 同上,未安装 mbstring 时的替代 |
grapheme_strlen() | 字素簇 | intl | 需要「用户感知的一个字符」时,如组合字符、部分 emoji 序列 |
count() | 元素个数 | 核心 | 数组、Countable对象 |
grapheme_strlen()是这条线上的终点:有些字符由多个码位组合而成(比如带变音符号的字母、由多个码位组成的 emoji 序列),mb_strlen()会把它们算成好几个,而用户看起来只有一个。如果业务真的要求「所见即所得」的字符数,需要 intl 扩展提供的grapheme_strlen()。
反过来也要强调一句边界:strlen()不能用来数数组的元素个数。count()才是干这个的,而且 PHP 8 起把数组传给strlen()会直接抛TypeError(PHP 7 只是返回null并发出警告),这个改动把一个静默的错误变成了显式的崩溃,对排查反而更友好。
常见坑点
- ❌ 用
strlen()校验中文字数:if (strlen($nickname) > 10)。✅ 一个汉字占 3 字节,应该用mb_strlen($nickname, 'UTF-8')。
- ❌ 用
substr()截断中文或 emoji,结果出现乱码,后面json_encode()还失败了。✅ 按字符切用mb_substr(),按字节切又不切坏字符用mb_strcut()。
- ❌ 担心
strlen()慢,于是到处手动缓存长度。✅ PHP 字符串自带长度字段,strlen()不扫描内容,开销与长度无关。
- ❌ 调用
mb_strlen()不传编码,默默依赖mb_internal_encoding()。✅ 显式写mb_strlen($s, 'UTF-8'),避免被 ini 或框架改动影响。
- ❌ 把数组传给
strlen()。✅ PHP 8 起抛TypeError;要数元素个数用count()。
- ❌ 用
strlen($s) === false判断字符是否为空。✅strlen()恒返回 int,空串就是0;判空直接写$s === ''。
- ❌ 用
strlen()校验数据库varchar字段的长度上限。✅varchar按字符计,要用mb_strlen()对齐;只有varbinary、blob之类才按字节算。
- ❌ 向
strlen()传null(读取未定义的参数时很可能发生)。✅ PHP 8.1 起该用法被废弃,先进判断再传,或用(string) $v显式转换。
总结
| 字节数 | strlen($s) | 二进制安全,内容含\0也不截断 |
| 字符数 | mb_strlen($s, 'UTF-8') | 需要 mbstring 扩展 |
| 字素簇数量 | grapheme_strlen($s) | 需要 intl 扩展 |
| 按字符截断 | mb_substr($s, 0, $n, 'UTF-8') | 结果始终是合法 UTF-8 |
| 按字节安全截断 | mb_strcut($s, 0, $n, 'UTF-8') | 不切断字符 |
| 判断非空 | $s !== '' | 比strlen($s) > 0更直接 |
| 数数组元素 | count($arr) | strlen()不能用于数组 |
| 校验合法性 | mb_check_encoding($s, 'UTF-8') | 用于确认截断后是否仍然合法 |
strlen()的全部要点就是一句话:它数的是字节,而且是不扫描内容地数。想清楚业务约束究竟是「几个字」还是「多少字节」,再对应地选mb_strlen()或者strlen(),中文环境下的长度问题基本就绝迹了;截断字符串时记得用mb_substr()或mb_strcut(),别让半个汉字流到下游去。