PHP字符串截取子串:掌握substr、mb_substr及高级实用技巧398


在PHP编程中,处理字符串是日常任务的核心部分。无论是从数据库中检索数据、解析用户输入,还是格式化输出内容,字符串的截取都是不可或缺的技能。本文将作为一份专业的指南,深入探讨PHP中截取子串的各种方法,从基础函数到处理多字节字符,再到高级的基于分隔符的截取策略,旨在帮助您高效、准确地完成字符串操作。

一、PHP截取子串的基础:substr()函数

substr()是PHP中最常用的字符串截取函数,它能够从字符串中返回指定长度的一部分。其语法简洁明了,但理解其参数的含义至关重要。

语法: substr(string $string, int $offset, ?int $length = null): string|false
$string:必需。要截取的原始字符串。
$offset:必需。从何处开始截取。

如果为正数,则从字符串的开头算起。第一个字符的位置是0。
如果为负数,则从字符串的末尾算起。-1表示最后一个字符。


$length:可选。截取字符串的最大长度。

如果为正数,则从$offset开始截取指定长度的子串。
如果为负数,则表示从$offset开始,到字符串末尾倒数$length个字符为止。
如果省略,则截取从$offset到字符串末尾的所有字符。



示例:<?php
$string = "Hello, World! PHP is great.";
// 1. 从开头截取固定长度
echo substr($string, 0, 5); // 输出: Hello
// 2. 从指定位置截取到末尾
echo substr($string, 7); // 输出: World! PHP is great.
// 3. 使用负数offset从末尾开始截取
echo substr($string, -6); // 输出: great.
// 4. 使用负数length,从指定位置开始,但不包含末尾的N个字符
echo substr($string, 7, -12); // 输出: World! PHP
// 5. offset超出现有长度,返回空字符串或false(根据PHP版本和上下文)
echo substr($string, 100); // 输出: (空字符串)
?>

注意事项: substr()函数是字节安全的,这意味着它会按字节进行截取。对于只包含单字节字符(如ASCII码)的字符串,这通常不是问题。但当字符串包含多字节字符(如中文、日文、韩文等UTF-8编码的字符)时,substr()可能会截断字符导致乱码。

二、处理多字节字符:mb_substr()函数

鉴于substr()在处理多字节字符时的局限性,PHP提供了mb_substr()函数,它是多字节字符串函数库(mbstring)的一部分,专门用于安全地处理不同字符编码的字符串。

语法: mb_substr(string $string, int $start, ?int $length = null, ?string $encoding = null): string|false
$string:必需。要截取的原始字符串。
$start:必需。从何处开始截取。与substr()的$offset类似,但这里的索引是按字符计算的,而非字节。
$length:可选。截取字符串的最大长度(按字符计算)。
$encoding:可选。要使用的字符编码。如果省略,则使用内部字符编码(可通过mb_internal_encoding()设置)。

示例:<?php
mb_internal_encoding("UTF-8"); // 推荐在应用入口设置内部编码
$chinese_string = "你好,世界!PHP编程真有趣。";
// 使用substr()可能会导致乱码
echo "substr截取(可能乱码):" . substr($chinese_string, 0, 6) . "<br>"; // 输出: 你好,世�
// 使用mb_substr()正确截取
echo "mb_substr截取: " . mb_substr($chinese_string, 0, 6) . "<br>"; // 输出: 你好,世界!
// 从指定位置开始截取
echo "从第4个字符开始截取: " . mb_substr($chinese_string, 4, 5) . "<br>"; // 输出: 世界!PHP
// 使用负数start
echo "从倒数第5个字符开始: " . mb_substr($chinese_string, -5) . "<br>"; // 输出: 编程真有趣。
// 显式指定编码
echo "显式指定UTF-8编码: " . mb_substr($chinese_string, 0, 3, 'UTF-8') . "<br>"; // 输出: 你好,
?>

最佳实践: 对于任何可能包含非ASCII字符(如中文、日文、韩文等)的字符串,总是推荐使用mb_substr()而非substr(),并且在应用程序的入口处设置好mb_internal_encoding(),以确保字符编码的一致性和正确性。

三、根据分隔符截取子串的实用技巧

在实际开发中,我们经常需要根据特定的分隔符来截取字符串,例如从URL中提取域名,或者从CSV行中获取特定字段。这里介绍几种常用的方法。

1. 截取某个字符之前或之后的部分:strpos() / strrpos() 结合 substr() / mb_substr()


strpos()用于查找字符串中第一次出现某个子串的位置,而strrpos()则查找最后一次出现的位置。

示例:截取邮箱地址的用户名和域名<?php
$email = "@";
$at_pos = strpos($email, "@"); // 找到'@'的位置
if ($at_pos !== false) {
$username = substr($email, 0, $at_pos);
$domain = substr($email, $at_pos + 1);
echo "用户名: " . $username . "<br>"; // 输出:
echo "域名: " . $domain . "<br>"; // 输出:
}
$url = "/path/to/?id=123";
$last_slash_pos = strrpos($url, '/'); // 找到最后一个'/'的位置
if ($last_slash_pos !== false) {
$path_before_file = substr($url, 0, $last_slash_pos + 1);
$file_name = substr($url, $last_slash_pos + 1);
echo "路径(不含文件名): " . $path_before_file . "<br>"; // 输出: /path/to/
echo "文件名: " . $file_name . "<br>"; // 输出: ?id=123
}
// 结合mb_substr处理多字节分隔符
mb_internal_encoding("UTF-8");
$long_text = "这是包含一个【重要信息】的示例文本。";
$start_tag_pos = mb_strpos($long_text, "【");
$end_tag_pos = mb_strpos($long_text, "】");
if ($start_tag_pos !== false && $end_tag_pos !== false && $end_tag_pos > $start_tag_pos) {
$content = mb_substr($long_text, $start_tag_pos + 1, $end_tag_pos - $start_tag_pos - 1);
echo "提取的内容: " . $content . "<br>"; // 输出: 重要信息
}
?>

2. 利用strstr() / strchr() / strrchr() 直接截取


这些函数在找到分隔符后,会返回从分隔符开始(或之前)到字符串末尾的部分。
strstr(string $haystack, string $needle, bool $before_needle = false): string|false:查找$needle的第一次出现,并返回从该点到字符串末尾的部分。如果$before_needle为true,则返回$needle之前的部分。
strchr():是strstr()的别名。
strrchr(string $haystack, string $needle): string|false:查找$needle的最后一次出现,并返回从该点到字符串末尾的部分。

示例:<?php
$full_url = "/";
// 截取域名部分(""之后)
$domain_part = strstr($full_url, "://");
if ($domain_part !== false) {
echo "域名部分(含协议):" . $domain_part . "<br>"; // 输出: :///
echo "仅域名:" . substr($domain_part, 3, strpos($domain_part, '/', 3) - 3) . "<br>"; // 输出:
}
// 获取文件扩展名
$file_name = "";
$extension = strrchr($file_name, ".");
echo "文件扩展名: " . $extension . "<br>"; // 输出: .pdf
// 获取@符号之前的部分(用户名)
$email_address = "admin@";
$username_part = strstr($email_address, "@", true);
echo "用户名: " . $username_part . "<br>"; // 输出: admin
?>

3. 使用正则表达式:preg_match()


当需要更复杂的匹配模式或提取多个部分时,正则表达式是强大的工具。preg_match()函数可以根据正则表达式匹配并提取子串。

语法: preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int|false
$pattern:要搜索的正则表达式。
$subject:要搜索的字符串。
$matches:可选。一个数组,如果提供了,则会填充所有匹配到的结果。$matches[0]是完整匹配,$matches[1]是第一个捕获组,依此类推。

示例:<?php
$html_content = "<div>你好</div><p>这是一个<strong>重要</strong>的段落。</p>";
// 提取<strong>标签中的内容
if (preg_match('/<strong>(.*?)<\/strong>/u', $html_content, $matches)) {
echo "提取的粗体内容: " . $matches[1] . "<br>"; // 输出: 重要
}
// 提取URL中的协议、域名和路径
$full_url_regex = ":8080/path/to/resource?query=1";
$regex = '#^(?P<protocol>https?):/\/(?P<domain>[a-zA-Z0-9.-]+)(?::(?P<port>\d+))?(?P<path>\/[^\?#]*)(?P<query>\?[^#]*)?(?P<fragment>#.*)?$#';
if (preg_match($regex, $full_url_regex, $url_parts)) {
echo "协议: " . $url_parts['protocol'] . "<br>"; // 输出: https
echo "域名: " . $url_parts['domain'] . "<br>"; // 输出:
echo "路径: " . $url_parts['path'] . "<br>"; // 输出: /path/to/resource
}
?>

注意: 使用正则表达式时,对于包含多字节字符的模式,需要在模式末尾添加u修饰符(UTF-8模式),如'/pattern/u',以确保正确匹配。

四、总结与最佳实践

掌握PHP字符串截取子串的技能对于任何PHP开发者来说都至关重要。正确选择和使用合适的函数,能够大大提高代码的效率和健壮性。
默认使用mb_substr(): 除非您能百分之百确定字符串只包含ASCII字符,否则为了避免乱码问题,请始终优先使用mb_substr()。务必在应用程序入口处设置mb_internal_encoding()。
理解参数含义: 无论是substr()还是mb_substr(),都要清晰理解$offset/$start和$length参数的含义,特别是负值的使用场景。
善用分隔符函数: 对于基于分隔符的截取,strpos()/strrpos()结合substr()/mb_substr(),或者直接使用strstr()/strrchr(),通常比复杂的正则表达式更高效和易读。
正则表达式的权衡: 当需求变得复杂,涉及多种模式匹配或需要从字符串中提取多个独立部分时,正则表达式是不可替代的强大工具。但请注意其学习成本和可能的性能开销。
错误处理: 字符串查找函数(如strpos())在未找到匹配时会返回false,因此在使用其返回值作为截取参数之前,务必进行严格的!== false判断。

通过本文的讲解,您应该已经全面掌握了PHP中截取子串的各种方法及其适用场景。在实际开发中灵活运用这些技巧,将使您的字符串处理工作更加得心应手。

2025-11-03


上一篇:PHP 数组尾部元素操作:方法、性能与最佳实践深度解析

下一篇:PHP Web应用中的客户端唯一标识:多维度设备ID获取策略与实践