PHP 字符串截取终极指南:从中间精准提取子串的多种高效方法与实用技巧167


在 PHP 开发中,字符串处理是日常工作中不可或缺的一部分。无论是解析用户输入、处理文件内容、分析日志数据,还是构建动态页面,我们经常需要对字符串进行截取、查找、替换等操作。其中,“截取字符串中间”的需求尤为常见,它通常意味着我们需要从一个较长的字符串中,根据固定的位置、特定的分隔符或复杂的模式,提取出我们所需的核心信息。本文将作为一份全面的指南,深入探讨 PHP 中截取字符串中间的各种方法,包括基础函数、多字节字符处理、结合查找函数,以及强大的正则表达式,并提供详细的代码示例和最佳实践,助您在各种场景下都能游刃有余。

一、理解字符串截取的核心:起始位置与长度

无论是截取字符串的哪个部分,其本质都离不开两个关键参数:起始位置 (start)截取长度 (length)。PHP 提供了 `substr()` 和 `mb_substr()` 这两个核心函数来完成基于位置的截取。

1.1 `substr()`:单字节字符串截取的基石


`substr()` 函数是 PHP 处理字符串的基石之一,它用于返回字符串的一部分。其基本语法如下:
string substr ( string $string , int $start [, int $length ] )


`$string`:要截取的原始字符串。
`$start`:截取的起始位置。

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


`$length`:可选参数,截取的长度。

正数:截取指定长度的子串。
负数:从字符串末尾向前数,直到这个位置结束截取(但不包括该位置)。
省略:从 `$start` 位置一直截取到字符串末尾。



示例 1:固定位置截取

假设我们有一个格式固定的字符串 `“YYYY-MM-DD HH:MM:SS”`,我们想提取其中的月份 `MM`。
$datetime = "2023-10-26 14:30:00";
// 月份 MM 位于索引 5,长度为 2
$month = substr($datetime, 5, 2);
echo "月份: " . $month; // 输出: 月份: 10

示例 2:从中间截取一部分到结尾

如果想截取日期部分 `MM-DD`
$date_part = substr($datetime, 5, 5); // 从索引5开始,截取5个字符
echo "日期部分: " . $date_part; // 输出: 日期部分: 10-26

使用负数参数截取:

负数 `start` 和 `length` 参数在某些场景下非常灵活。例如,截取 `HH:MM:SS` 部分:
$time_part = substr($datetime, -8); // 从倒数第8个字符开始截取到末尾
echo "时间部分: " . $time_part; // 输出: 时间部分: 14:30:00
$minutes = substr($datetime, -5, 2); // 从倒数第5个字符开始截取2个字符
echo "分钟: " . $minutes; // 输出: 分钟: 30

1.2 `mb_substr()`:多字节字符串的救星


`substr()` 函数在处理包含中文、日文、韩文等多字节字符的字符串时会遇到问题,因为它会将每个字节视为一个字符。这会导致乱码或截取不准确。为了解决这个问题,PHP 提供了 `mb_substr()` 函数,它是 `substr()` 的多字节版本。
string mb_substr ( string $string , int $start [, int $length [, string $encoding ]] )


参数与 `substr()` 类似,但额外增加了 `$encoding` 参数。
`$encoding`:可选参数,指定字符串的字符编码,如 `'UTF-8'`, `'GBK'` 等。如果省略,则使用内部字符编码(可通过 `mb_internal_encoding()` 设置)。

示例 3:多字节字符截取
$chinese_string = "你好,世界!PHP编程很有趣。";
// 错误使用 substr()
$wrong_cut = substr($chinese_string, 4, 6); // 可能会截取到半个汉字,导致乱码
echo "错误截取: " . $wrong_cut; // 输出取决于环境,可能乱码或不符合预期
// 正确使用 mb_substr()
mb_internal_encoding("UTF-8"); // 明确设置内部编码
$correct_cut = mb_substr($chinese_string, 4, 3); // 从第4个字符(索引3)开始截取3个字符
echo "正确截取: " . $correct_cut; // 输出: 正确截取: 世界!
$program_part = mb_substr($chinese_string, 7, 7); // 截取“PHP编程很有趣”
echo "编程部分: " . $program_part; // 输出: 编程部分: PHP编程很有趣

最佳实践:在处理任何可能包含多字节字符的字符串时,请始终优先使用 `mb_substr()` 及其他 `mb_*` 系列函数,并确保设置正确的字符编码。

二、基于分隔符截取字符串中间内容

在实际应用中,我们很少能直接知道要截取内容的精确起始位置和长度。更多时候,我们知道的是要提取的内容被特定的起始分隔符结束分隔符包围。例如,从 `[data:123]` 中提取 `123`,或从 `URL?id=456&name=foo` 中提取 `id` 的值 `456`。

处理这类需求,我们需要结合字符串查找函数 (`strpos()`, `strrpos()`, `strstr()`) 和截取函数 (`substr()`, `mb_substr()`)。

2.1 `strpos()` 和 `substr()` 的组合:最常见也是最基础的方法


`strpos()` 函数用于查找一个字符串在另一个字符串中首次出现的位置。结合它,我们可以精确地定位到分隔符,然后计算出目标内容的起始位置和长度。
int|false strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )


`$haystack`:要搜索的原始字符串。
`$needle`:要查找的子串(分隔符)。
`$offset`:可选参数,从 `$haystack` 的哪个位置开始搜索。

如果找到,返回子串的起始位置(索引);如果未找到,返回 `false`。

示例 4:提取两个固定分隔符之间的内容

我们想从字符串 `"[username:john_doe]" `中提取 `john_doe`。
$input_string = "[username:john_doe]";
$start_delimiter = "username:";
$end_delimiter = "]";
// 1. 查找起始分隔符的位置
$start_pos = strpos($input_string, $start_delimiter);
// 2. 检查是否找到起始分隔符
if ($start_pos !== false) {
// 3. 计算实际内容开始的位置(跳过起始分隔符的长度)
$content_start_index = $start_pos + strlen($start_delimiter);
// 4. 查找结束分隔符的位置,从内容开始的位置之后进行查找
$end_pos = strpos($input_string, $end_delimiter, $content_start_index);
// 5. 检查是否找到结束分隔符
if ($end_pos !== false) {
// 6. 计算内容的长度
$length = $end_pos - $content_start_index;
// 7. 截取内容
$username = substr($input_string, $content_start_index, $length);
echo "用户名: " . $username; // 输出: 用户名: john_doe
} else {
echo "错误:未找到结束分隔符。";
}
} else {
echo "错误:未找到起始分隔符。";
}

多字节版本:`mb_strpos()` 和 `mb_substr()`

如果分隔符或被截取内容包含多字节字符,则需要使用 `mb_strpos()` 和 `mb_substr()`。
$log_entry = "【用户通知】您的订单号:202310261234 已成功提交。";
$start_delimiter = "订单号:";
$end_delimiter = " 已成功";
mb_internal_encoding("UTF-8");
$start_pos = mb_strpos($log_entry, $start_delimiter);
if ($start_pos !== false) {
$content_start_index = $start_pos + mb_strlen($start_delimiter);
$end_pos = mb_strpos($log_entry, $end_delimiter, $content_start_index);
if ($end_pos !== false) {
$length = $end_pos - $content_start_index;
$order_id = mb_substr($log_entry, $content_start_index, $length);
echo "订单号: " . $order_id; // 输出: 订单号: 202310261234
} else {
echo "错误:未找到结束分隔符。";
}
} else {
echo "错误:未找到起始分隔符。";
}

2.2 `strrpos()`:从字符串末尾查找


`strrpos()` 函数与 `strpos()` 类似,但它从字符串末尾开始向前查找,返回最后一次出现的子串位置。这在需要查找最后一个分隔符的场景中非常有用。
int|false strrpos ( string $haystack , mixed $needle [, int $offset = 0 ] )

例如,从文件路径 `/var/www/html/assets/images/` 中提取文件名 ``:
$filepath = "/var/www/html/assets/images/";
$last_slash_pos = strrpos($filepath, '/');
if ($last_slash_pos !== false) {
$filename = substr($filepath, $last_slash_pos + 1);
echo "文件名: " . $filename; // 输出: 文件名:
}

当然,对于文件路径,PHP 提供了更专业的 `basename()` 函数,但这里展示 `strrpos()` 的用法。

2.3 `strstr()` 和 `stristr()`:截取到或从特定子串开始


`strstr()` 函数(以及其不区分大小写的版本 `stristr()`)可以查找一个字符串,并返回从这个子串开始到字符串结尾的部分,或者从字符串开始到这个子串之间的部分。这在某些特定场景下能简化代码。
string|false strstr ( string $haystack , mixed $needle [, bool $before_needle = false ] )


`$before_needle`:如果设置为 `true`,则返回 `needle` 出现之前的部分。

示例 5:使用 `strstr()` 提取 `id` 参数的值

假设我们有一个 URL `/page?name=test&id=123&type=A`,想提取 `id` 的值。
$url = "/page?name=test&id=123&type=A";
$id_start = strstr($url, "id="); // 找到 "id=" 及之后的部分
if ($id_start !== false) {
// $id_start 现在是 "id=123&type=A"
$id_value_and_rest = substr($id_start, strlen("id=")); // 得到 "123&type=A"
$id_end_pos = strpos($id_value_and_rest, '&'); // 查找下一个 '&'
if ($id_end_pos !== false) {
$id = substr($id_value_and_rest, 0, $id_end_pos);
} else {
// 如果没有 '&',说明 id 是最后一个参数
$id = $id_value_and_rest;
}
echo "ID: " . $id; // 输出: ID: 123
}

这种方法虽然可行,但在处理 URL 参数时,`parse_url()` 和 `parse_str()` 函数会更加强大和健壮。

三、正则表达式:处理复杂模式的利器 `preg_match()`

当分隔符不固定、内容模式复杂、或者需要同时提取多个部分时,正则表达式(Regular Expressions)是最高效、最灵活的工具。PHP 提供了 PCRE (Perl Compatible Regular Expressions) 函数族,其中 `preg_match()` 是最常用的匹配函数。
int|false preg_match ( string $pattern , string $subject , array &$matches = null , int $flags = 0 , int $offset = 0 )


`$pattern`:要匹配的正则表达式。
`$subject`:要搜索的字符串。
`$matches`:可选参数,用于存储所有匹配结果的数组。

`$matches[0]` 存放完整匹配到的字符串。
`$matches[1]` 存放第一个捕获组匹配到的字符串。
`$matches[2]` 存放第二个捕获组匹配到的字符串,以此类推。



如果找到匹配,返回 1;如果未找到,返回 0;如果发生错误,返回 `false`。

3.1 使用捕获组提取中间内容


正则表达式的核心在于定义模式,并通过捕获组 `()` 来指定我们想要提取的部分。

示例 6:提取 XML 标签中的内容

从 `PHP Programming` 中提取 `PHP Programming`。
$xml_string = "PHP ProgrammingJohn Doe";
// 正则解释:
// ``:匹配字面字符串
// `(.*?)`:第一个捕获组,匹配任意字符(.),零次或多次(*),非贪婪模式(?)
// ``:匹配字面字符串
$pattern = "/(.*?)/";
if (preg_match($pattern, $xml_string, $matches)) {
$title = $matches[1]; // 捕获组1的内容
echo "标题: " . $title; // 输出: 标题: PHP Programming
} else {
echo "未找到标题。";
}

非贪婪模式 `?` 的重要性: `.*?` 确保匹配尽可能少的字符,直到遇到下一个模式。如果没有 `?`,`.*` 是贪婪模式,会匹配到最后一个 ``。

示例 7:提取特定格式的 ID

从 `User ID: 12345, Transaction ID: 67890.` 中提取 `Transaction ID` 的值。
$log_entry = "User ID: 12345, Transaction ID: 67890.";
// 正则解释:
// `Transaction ID: `:匹配字面字符串
// `(\d+)`:捕获组,匹配一个或多个数字(\d+)
// `\.`:匹配字面字符 . (需要转义,因为 . 在正则表达式中有特殊含义)
$pattern = "/Transaction ID: (\d+)\./";
if (preg_match($pattern, $log_entry, $matches)) {
$transaction_id = $matches[1];
echo "交易ID: " . $transaction_id; // 输出: 交易ID: 67890
} else {
echo "未找到交易ID。";
}

3.2 使用前瞻/后瞻断言 (`(?

2025-11-06


上一篇:PHP数组交集:深度解析内置函数与自定义实现,提升数据处理效率

下一篇:WAMP Server PHP开发入门:从环境搭建到第一个PHP文件创建与运行