直接用 strlen() 会出错,因为它统计字节数而非字符数,如 '你好' 返回 6 而非字符数 2,导致截断乱码、分页错位、验证失败等问题;应改用 mb_strlen($str, 'UTF-8') 等多字节安全函数。

为什么直接用 strlen() 会出错?
因为 strlen() 统计的是字节数,不是字符数。遇到中文、emoji、繁体字等多字节字符(UTF-8 下一个汉字占 3 字节),strlen('你好') 返回 6,但你真正想问的是“有几个字符”——答案是 2。
不加区分地混用会导致:截断乱码、分页错位、表单验证失败、JSON 输出被截半。
- 数据库字段长度限制按字符算(如
VARCHAR(10)指 10 个字符),但用substr()截取时若按字节切,可能只拿到半个汉字 -
strpos()在含中文字符串中搜索英文关键词,可能因偏移错位返回false(实际存在) - PHP 默认函数在非 ASCII 环境下行为不可靠,尤其当
mbstring.func_overload关闭(现代 PHP 默认关闭,不能依赖)
mb_strlen() 和 mb_substr() 怎么安全替换?
必须显式指定编码,不能依赖 mb_internal_encoding() 全局设置——它可能被其他组件修改,或在 CLI/SAPI 切换时失效。
- 统一用
mb_strlen($str, 'UTF-8')替代strlen($str) - 截取用
mb_substr($str, 0, 10, 'UTF-8'),第三个参数是字符数,不是字节数 - 注意:
mb_substr()的起始位置和长度单位都是「字符」,而substr()是「字节」,二者数值不可互换 - 如果源字符串编码不确定,先用
mb_detect_encoding($str, ['UTF-8', 'GB2312', 'BIG5'], true)探测,再转成 UTF-8 处理
哪些常用函数有 mb_* 对应版本?
不是所有字符串函数都有 mb_* 版本,部分需组合或改写逻辑:
立即学习“PHP免费学习笔记(深入)”;
- 大小写转换:
mb_strtoupper($str, 'UTF-8')/mb_strtolower($str, 'UTF-8')(strtoupper()对中文无效) - 查找位置:
mb_strpos($haystack, $needle, 0, 'UTF-8')(第四个参数是编码,别漏) - 替换:
mb_ereg_replace()已废弃;改用mb_ereg_replace()不推荐 → 用mb_substr()+mb_strpos()手动实现,或升级到 PCRE2 的preg_replace()配合u修饰符(/pattern/u) - 分割:
mb_split()已废弃;改用preg_split('/./u', $str, -1, PREG_SPLIT_NO_EMPTY)或mb_str_split($str, 1, 'UTF-8')(PHP 7.4+) - 无对应版:
trim()、ltrim()、rtrim()—— 它们对多字节字符安全,可继续用(因为只操作首尾空白,不涉及字符边界)
上线前必须检查的三个兼容性陷阱
即使写了 mb_* 函数,仍可能翻车:
-
mbstring扩展未启用:用extension_loaded('mbstring')检查,否则直接 fatal error;CI/CD 环境常遗漏此扩展 - MySQL 连接未设 UTF-8:即使 PHP 用
mb_*处理了字符串,mysqli_set_charset($conn, 'utf8mb4')或 DSN 中加;charset=utf8mb4缺一不可,否则存入仍是乱码 - 文件读写未声明编码:用
file_get_contents()读取 UTF-8 文件没问题,但用fgets()逐行读时,若文件含 BOM 或混合编码,mb_strlen()可能误判;建议统一用mb_convert_encoding(file_get_contents($path), 'UTF-8', 'auto')
最易忽略的是:把 mb_* 当万能药,却忘了 HTTP 请求头、HTML meta、JS encodeURIComponent 层层编码是否一致——多字节安全从来不是单点问题。











