php中取指定位置子串应优先用mb_substr()处理utf-8字符串,因substr()按字节偏移易致中文乱码;需配合mb_strlen()计算字符长度,并注意grapheme级切分及边界校验。

用 substr() 取指定偏移位置的子串最直接
PHP 里没有“字符索引访问”这种语法(比如 $str[5] 在多字节字符串里常出错),substr() 是安全取子串的首选。它按字节偏移计算,对纯 ASCII 没问题;但遇到中文、emoji 等 UTF-8 字符时,一个字符占多个字节,直接传字节偏移会切在中间,导致乱码。
实操建议:
- 如果确定字符串是 ASCII 或已知单字节编码,直接用
substr($str, $start, $length)——$start是字节起始位置,从 0 开始 - 处理中文/UTF-8 字符串,必须先用
mb_substr($str, $start, $length, 'UTF-8'),否则大概率得到 -
$length为null或省略时,截取到末尾;传负数表示从末尾倒数(如-2表示去掉最后 2 字节) - 注意:空字符串或越界偏移不会报错,而是返回空字符串或尽可能截取——容易掩盖逻辑错误
mb_strlen() 和 mb_substr() 必须配对用
很多人只记得换 mb_substr(),却忘了算长度也得用 mb_strlen()。比如想取第 3 个汉字开始的 2 个字:substr($str, 3, 2) 是错的,因为前 3 个汉字可能占 9 字节;正确做法是 mb_substr($str, 3, 2, 'UTF-8'),且前提是位置 3 是以字符为单位的偏移。
常见错误现象:
立即学习“PHP免费学习笔记(深入)”;
- 用
strlen()得到长度 12,以为能安全遍历 0~11,结果substr($str, 5, 1)返回半个中文 - 用
strpos()找到位置后直接喂给substr()——strpos()返回的是字节偏移,不是字符偏移 - 没显式指定编码(第 4 参数),依赖
mb_internal_encoding()默认值,线上环境可能不一致
要精确按“视觉位置”切字符串?先转数组再操作
某些场景下,“第 5 个位置”指的是用户看到的第 5 个字形(grapheme),比如 “??” 这种组合 emoji 实际是多个 Unicode 码点。这时 mb_substr() 仍可能切开它。
实操建议:
- 用
grapheme_extract($str, $size, GRAPHEME_EXTR_COUNT, $pos)提取指定数量的字形单元,$pos是字节偏移,需配合grapheme_strlen()换算 - 更简单粗暴但可靠的做法:用
preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY)把字符串拆成字符数组,再用数组下标取 —— 适合长度不大的字符串(性能差,别在循环里用) - 避免用
str_split($str),它按字节切,UTF-8 下完全不可靠
偏移量超出范围时的行为很安静,容易漏掉边界检查
substr() 和 mb_substr() 对负数起始、超长长度都静默处理,不报错也不警告。比如 mb_substr("abc", 10, 2) 返回空字符串,而不是抛异常或提示越界。
容易踩的坑:
- 从数据库或 API 拿到的偏移量没校验,直接传入,结果后续逻辑拿到空串却继续执行
- 用
mb_strpos()搜索失败返回false,直接当整数传给mb_substr(),变成从位置 0 开始截(因为false == 0) - 推荐写法:
$pos = mb_strpos($str, $needle); if ($pos === false) { /* 处理未找到 */ } else { $part = mb_substr($str, $pos, 1); }
实际项目里,90% 的“取指定位置字符”需求,本质是“取第 N 个字符”,不是“取字节偏移为 X 的位置”。别绕开 mb_* 函数去手动算字节,也别默认字符串是 ASCII。UTF-8 是常态,不是例外。











