PHP 截取字符串的函数有四五個,选错的一个直接表现就是中文变问号或长度算错。本文用最小示例讲清 substr、mb_substr 和按分隔符截取三种思路,重点说明为什么中文必须用多字节版本、以及负数长度该怎么理解。核心结论是:处理中文一律用 mb_substr,处理英数用 substr,按分隔符切段用 explode。下面每段代码都给出逐行注释、预期输出和常见失败。先确认 PHP 版本与文件编码,再决定用哪种。

一、先看结论:三种思路怎么选
下面这张表给出三种函数的适用场景和注意点。判断依据是「字符串里有没有中文」和「要不要保留分隔符」。
| 思路 | 适用场景 | 注意点 |
|---|---|---|
substr |
纯英文、数字、符号 | 按字节切,遇到中文会截断成乱码 |
mb_substr |
含中文或多字节字符 | 需 mbstring 扩展,长度按字符算 |
explode 配合下标 |
按分隔符截取指定段 | 分隔符不会保留,段数不足会越界 |

需要提醒的是,substr 把中文当一个字节处理,截断处多出一个字节就会显示成问号,而且不报错。想在线对比不同写法,可用 在线代码实例 验证实际结果。
二、环境确认与最小示例
先确认 PHP 版本和扩展,再写最小示例。示例基于 PHP 7.x 与 8.x,mb_substr 在两者中行为一致;PHP 8.0 起 substr 返回类型由 string|false 调整为 string,对判空逻辑有影响。以下为预期结果说明,未在本机执行,按 PHP 官方文档推断。
# 查看 PHP 版本,并确认 mbstring 扩展可用
php -v
php -m | grep mbstring
下面是一份最小示例,一段含中文和英文的混合字符串,用来对比三种写法的差别。PHP 版本差异对截取结论没有影响,但对函数返回类型的判断有影响。
<?php
// 待截取的混合字符串,含中文与英文
$text = \"编程狮 PHP 字符串截取示例\";
// 用 substr 截取前 6 个字节
echo substr($text, 0, 6) . \"\\n\";
// 中文按字节处理,6 字节只够两个汉字加一个空格,预期截出乱码或半截内容
// 用 mb_substr 截取前 6 个字符
echo mb_substr($text, 0, 6, \'UTF-8\') . \"\\n\";
// 按字符计数,预期截出\"编程狮 PHP\"
这两行的差别就是byte与字符的差别:substr 数的是字节,mb_substr 数的是字符。想系统补齐 PHP 基础,可以看 PHP 教程。处理中文优先选多字节函数,处理英文数字用内置函数就够,这两类问题的边界本来就不一样。
三、思路一:substr 按字节截取
substr 是 PHP 内置默认函数,按字节计数,处理英文和数字很顺手,遇到中文就会出问题。
<?php
$text = \"abcdefg\";
// 正数长度:从起始位置向后取指定长度
echo substr($text, 0, 3) . \"\\n\"; // 预期 abc
// 负数长度:从末尾向前保留指定长度,常用来截去掉后缀
echo substr($text, 0, -3) . \"\\n\"; // 预期 ab
// 省略长度:一直取到字符串末尾
echo substr($text, 2) . \"\\n\"; // 预期 cdefg
负数长度是这里最容易混淆的地方,它的含义是「从结尾往前数保留几个」,不是「倒数第几个开始」。判定标准是:一旦字符串里出现中文,就别再用 substr。还有一处边界值得单独记下来:起始位置超出字符串长度时,substr 在 PHP 7 及以前返回 false,PHP 8 起返回空字符串。这个差异会影响判空逻辑——原来写 if ($s === false) 的写法在 8.0 上会失效,改成 if ($s === \"\") 则两个版本都成立。升级项目时这类隐式变化最容易漏,建议在截取之后统一按字符串处理。修复方向是换成 mb_substr,并把字符编码显式传进去。
四、思路二:mb_substr 按字符截取
mb_substr 属于多字节安全函数,按字符计数,中文场景的默认选择。
<?php
$text = \"编程狮 PHP 字符串截取示例\";
// 用法与 substr 一致,但长度按字符算,需指定编码
echo mb_substr($text, 0, 6, \'UTF-8\') . \"\\n\"; // 预期\"编程狮 PHP\"
echo mb_substr($text, 0, -4, \'UTF-8\') . \"\\n\"; // 负数长度同样从末尾保留
// 常见失败:忘了传编码,函数按默认内部编码处理,中文仍可能出错
echo mb_substr($text, 0, 3) . \"\\n\"; // 未传编码时结果不确定
第二个调用是典型失败:不传第四个参数时,函数使用 mb_internal_encoding() 的默认值,若内部编码与文件实际编码不一致,截取结果就可能出错。修复方向是每次都显式传入 \'UTF-8\',不依赖全局设置。这个习惯能挡掉绝大多数编码类问题。
五、思路三:explode 按分隔符截取
按分隔符截取用 explode 切段再取下标,适合路径、日志这类有固定结构的文本。
<?php
// 用分隔符切成数组,再按下标取需要的那段
$path = \"upload/2026/09/report.txt\";
$parts = explode(\'/\', $path); // 按斜杠切段
echo $parts[0] . \"\\n\"; // 预期 upload
echo end($parts) . \"\\n\"; // 预期 report.txt,取最后一段
// 安全写法:段数不足时先判断,避免下标越界
if (count($parts) >= 3) {
echo $parts[2] . \"\\n\"; // 预期 09
}
explode 不保留分隔符,这是它与 substr 的主要差别。还有一点补充:切出来的元素全是字符串,后续要做数值比较时记得先转换类型,PHP 在字符串与数字比较时有一套自己的规则,直接比未必得到预期结果。另一个边界是段数不足:分隔符不存在时只会得到一个元素,直接按下标取会报未定义偏移量。判定标准是「你确定分隔符一定存在吗」,不确定就先 count 判断,这也是最容易在生产环境暴露的失败点。常见排错按下面清单走:
| 现象 | 常见原因 | 修复方向 |
|---|---|---|
| 中文截出问号或乱码 | 用了 substr,按字节切坏了 |
换成 mb_substr 并显式传 UTF-8 |
| 取长度比预期多一倍 | 按字节数当成了字符数 | 中文场景统一用 mb_substr |
返回 false 未达预期 |
substr 起始位置超出字符串长度 |
先确认起始位置,或用 mb_substr |
| 报未定义偏移量 | explode 后段数不足就取下标 |
取前先 count 判断,或检查分隔符是否存在 |
以上失败与修复均来自真实边界场景,未在本机执行,按 PHP 官方手册推断。想看字符串处理的更多技巧,可读 PHP 正则处理字符串。函数选错时通常不会报错,只会把结果悄悄截短或截断坏,这类失败只能靠对比预期输出发现。
总结
PHP 字符串截取的选择标准很清晰:含中文或多字节字符一律用 mb_substr 并显式传编码,纯英文数字用 substr,按分隔符切段用 explode。三个边界要记牢:负数长度表示从末尾保留,不是倒着数;不传编码的 mb_substr 仍可能出错;explode 切完必须先判断段数再取下标。动手前确认 PHP 版本与 mbstring 扩展可用,这三类问题就不会再出现。
延伸学习
想在编程狮系统学 PHP 字符串处理,可以顺着下面三篇深入:
常见问题
Q:处理中文为什么一定要用 mb_substr?
A:因为 substr 按字节计数,一个中文在 UTF-8 下占三个字节,按字节截断会把汉字切开,多出的残留字节显示成问号。多字节函数按字符计数,才不会出现半截汉字。
Q:mb_substr 不传编码行不行?
A:不传不好。函数会取 mb_internal_encoding() 的默认值,默认值与文件实际编码不一致时结果就可能出错。养成显式传 \'UTF-8\' 的习惯,比调整全局设置更可靠。
Q:substr($str, 0, -3) 到底截出来什么?
A:从开头取到「倒数第 3 个字符之前」,也就是保留末尾 3 个字符。它不是从倒数第 3 位开始截取,这是最容易理解错的地方。
Q:explode 之后下标取不到,怎么办?
A:先判断段数。分隔符不存在时只返回一个元素,直接取下标会报未定义偏移量。取前用 count 判断,或用 end() 取最后一段,两者都比硬取下标高。

TRAE-AI编程



