PHP字符串查找指定字符:从基础到高级,掌握多种高效方法215

```php

在PHP编程中,对字符串进行操作是日常开发的核心任务之一。无论你是需要验证用户输入、解析文件内容、处理API响应,还是从一大段文本中提取特定信息,查找字符串中的指定字符或子串都是必不可少的一步。本篇文章将作为一份详尽的指南,带你深入了解PHP中查找字符串指定字符的各种方法,从基础函数到高级正则表达式,再到多字节字符处理,帮助你选择最适合你需求的工具。

一、基础字符串查找:判断是否存在及首次出现位置

PHP提供了多款内置函数,可以高效地完成字符串的查找任务。它们主要分为两大类:判断字符或子串是否存在,以及获取其首次出现的位置。

1.1 strpos() 与 stripos():查找首次出现的位置


strpos() 函数是PHP中最常用也是最基本的字符串查找函数。它用于查找一个字符串在另一个字符串中首次出现的位置(索引)。
语法: strpos(string $haystack, string $needle, int $offset = 0): int|false
参数:

$haystack:在哪个字符串中进行查找(即“干草堆”)。
$needle:要查找的字符或子串(即“针”)。
$offset(可选):从 $haystack 的哪个位置开始查找,默认为0(从开头查找)。


返回值: 如果找到,返回 $needle 在 $haystack 中首次出现的位置(从0开始的索引);如果未找到,返回 false。

重要提示: strpos() 返回的索引可能为 0,而 0 在PHP的宽松比较中会被视为 false。因此,在判断是否找到时,务必使用严格比较运算符 === 或 !==。<?php
$text = "Hello, world! Welcome to PHP programming.";
$search1 = "world";
$search2 = "PHP";
$search3 = "Python";
$search4 = "o";
// 查找子串
if (strpos($text, $search1) !== false) {
echo "'{$search1}' 找到了,位置在:" . strpos($text, $search1) . "<br>"; // 输出 'world' 找到了,位置在:7
} else {
echo "'{$search1}' 未找到。<br>";
}
if (strpos($text, $search2) !== false) {
echo "'{$search2}' 找到了,位置在:" . strpos($text, $search2) . "<br>"; // 输出 'PHP' 找到了,位置在:20
} else {
echo "'{$search2}' 未找到。<br>";
}
// 未找到的情况
if (strpos($text, $search3) !== false) {
echo "'{$search3}' 找到了。<br>";
} else {
echo "'{$search3}' 未找到。<br>"; // 输出 'Python' 未找到。
}
// 查找单个字符并演示起始位置0的情况
$text2 = "apple";
$search5 = "a";
if (strpos($text2, $search5) !== false) {
echo "'{$search5}' 在 '{$text2}' 中找到了,位置在:" . strpos($text2, $search5) . "<br>"; // 输出 'a' 在 'apple' 中找到了,位置在:0
}
// 使用 offset 参数
$text3 = "ababab";
$search6 = "b";
$firstB = strpos($text3, $search6); // 1
$secondB = strpos($text3, $search6, $firstB + 1); // 3
echo "第一个 'b' 的位置: {$firstB}<br>"; // 输出 1
echo "第二个 'b' 的位置: {$secondB}<br>"; // 输出 3
?>

stripos() 函数与 strpos() 完全相同,唯一的区别是 stripos() 进行的是不区分大小写的查找。<?php
$text = "Hello, world! Welcome to PHP programming.";
$searchUpper = "php"; // 小写
$searchLower = "PHP"; // 大写
if (strpos($text, $searchUpper) !== false) {
echo "strpos 区分大小写,找到了小写 '{$searchUpper}'。<br>"; // 未找到
} else {
echo "strpos 区分大小写,未找到小写 '{$searchUpper}'。<br>"; // 输出 strpos 区分大小写,未找到小写 'php'。
}
if (stripos($text, $searchUpper) !== false) {
echo "stripos 不区分大小写,找到了小写 '{$searchUpper}',位置在:" . stripos($text, $searchUpper) . "<br>"; // 输出 stripos 不区分大小写,找到了小写 'php',位置在:20
}
if (stripos($text, $searchLower) !== false) {
echo "stripos 不区分大小写,找到了大写 '{$searchLower}',位置在:" . stripos($text, $searchLower) . "<br>"; // 输出 stripos 不区分大小写,找到了大写 'PHP',位置在:20
}
?>

1.2 strstr() 与 stristr():获取子串及之后的部分


如果你不仅需要知道子串是否存在,还想获取从该子串开始到字符串结尾的所有内容,那么 strstr() 或 stristr() 会是更好的选择。
语法: strstr(string $haystack, string $needle, bool $before_needle = false): string|false
参数:

$haystack:在哪个字符串中进行查找。
$needle:要查找的字符或子串。
$before_needle(可选):如果设置为 true,则返回 $needle 之前的部分;默认为 false,返回 $needle 及之后的部分。


返回值: 如果找到,返回从 $needle 开始到 $haystack 结尾的子字符串(或之前的部分);如果未找到,返回 false。

stristr() 同样是 strstr() 的不区分大小写版本。<?php
$email = "user@";
$domain = strstr($email, "@"); // "@"
echo "邮箱域名部分(包含@):" . $domain . "<br>";
$username = strstr($email, "@", true); // "user"
echo "邮箱用户名部分:" . $username . "<br>";
$text = "I love PHP and Javascript.";
$search = "php";
// 区分大小写查找
$result1 = strstr($text, $search);
echo "strstr (区分大小写): " . ($result1 ? $result1 : "未找到") . "<br>"; // 未找到
// 不区分大小写查找
$result2 = stristr($text, $search);
echo "stristr (不区分大小写): " . ($result2 ? $result2 : "未找到") . "<br>"; // and Javascript.
?>

二、查找最后一次出现的位置:strrpos() 与 strripos()

有时候,我们需要查找一个字符或子串在字符串中最后一次出现的位置,这时 strrpos() 和 strripos() 就派上用场了。
语法: strrpos(string $haystack, string $needle, int $offset = 0): int|false
strrpos() 从字符串末尾开始向前查找 $needle 首次出现的位置(但返回的索引仍是从开头计算)。
strripos() 是其不区分大小写版本。

<?php
$path = "/usr/local/bin/php/";
$slashPos = strrpos($path, "/"); // 18
echo "最后一个斜杠的位置:" . $slashPos . "<br>";
$filename = substr($path, $slashPos + 1); //
echo "文件名:" . $filename . "<br>";
$text = "apple pie and pineapple pizza";
$search = "apple";
$lastApple = strrpos($text, $search); // 16
echo "最后一个 'apple' 的位置:" . $lastApple . "<br>";
// 使用 offset 参数
// offset 为正数时,从指定位置开始往后找(正向查找),但仍返回最后一个
$text2 = "barbecue";
$pos1 = strrpos($text2, "b", 0); // 0 (第一个 'b')
$pos2 = strrpos($text2, "b", 1); // 4 (从索引1开始,找到'barbecue'中的第二个'b')
echo "strrpos('barbecue', 'b', 0): {$pos1}<br>";
echo "strrpos('barbecue', 'b', 1): {$pos2}<br>";
// offset 为负数时,从字符串末尾向前数 offset 个字符,然后从该位置开始向前查找
$text3 = "Mississippi";
$lastS = strrpos($text3, "s"); // 6
$lastS_offset = strrpos($text3, "s", -4); // 5 (从倒数第4个字符 'i' 开始向前找,找到索引为5的 's')
echo "最后一个 's' 的位置:{$lastS}<br>";
echo "倒数第4个字符之前最后一个 's' 的位置:{$lastS_offset}<br>";
?>

三、统计字符或子串出现次数:substr_count()

如果你的目标是统计一个子串在另一个字符串中出现的次数,substr_count() 是最直接高效的方法。
语法: substr_count(string $haystack, string $needle, int $offset = 0, ?int $length = null): int
参数:

$haystack:在哪个字符串中进行查找。
$needle:要查找的子串。
$offset(可选):从 $haystack 的哪个位置开始查找,默认为0。
$length(可选):在 $haystack 的哪个长度范围内查找。


返回值: $needle 在 $haystack 中出现的次数。

注意: substr_count() 不会计算重叠的子串。例如,在 "aaaaa" 中查找 "aaa" 只会返回 1。<?php
$text = "banana banana split banana shake";
$count = substr_count($text, "banana"); // 3
echo "'banana' 出现了 {$count} 次。<br>";
$text2 = "aaaaa";
$count2 = substr_count($text2, "aaa"); // 1 (不会计算重叠的)
echo "'aaa' 在 'aaaaa' 中出现了 {$count2} 次。<br>";
$text3 = "orange apple orange grape orange";
$count3 = substr_count($text3, "orange", 10, 15); // 从索引10开始,长度15的范围内查找
echo "指定范围内 'orange' 出现了 {$count3} 次。<br>"; // 1 (只找到最后一个)
?>

四、处理多字节字符:MBString 扩展函数

上述所有标准字符串函数(strpos(), stripos(), strstr(), substr_count() 等)都是字节安全的,而不是字符安全的。这意味着它们会将每个字节视为一个“字符”进行处理。对于使用UTF-8等变长编码的中文、日文、韩文或其他特殊字符,一个字符可能由多个字节组成,这会导致这些函数在处理时出现错误的结果。

为了正确处理多字节字符,PHP提供了 MBString(Multi-Byte String)扩展。你需要确保你的PHP环境中已启用此扩展(在 中查找 extension=mbstring)。

MBString 扩展提供了与标准字符串函数功能相同,但以 mb_ 开头的一系列函数,例如 mb_strpos(), mb_stripos(), mb_strrpos(), mb_substr_count() 等。这些函数通常接受一个额外的 $encoding 参数,用于指定字符串的字符编码。<?php
$text = "你好世界,Hello World!";
$char = "世界"; // 一个中文字符串
$encoding = "UTF-8";
// 使用 strpos (字节安全,非字符安全)
$pos_strpos = strpos($text, $char);
echo "strpos 查找 '{$char}':";
if ($pos_strpos !== false) {
echo "找到了,位置在:" . $pos_strpos . "<br>"; // 找到了,位置在:6 (这是字节偏移量,一个中文占3个字节)
} else {
echo "未找到。<br>";
}
// 使用 mb_strpos (字符安全)
$pos_mb_strpos = mb_strpos($text, $char, 0, $encoding);
echo "mb_strpos 查找 '{$char}':";
if ($pos_mb_strpos !== false) {
echo "找到了,位置在:" . $pos_mb_strpos . "<br>"; // 找到了,位置在:2 (这是字符偏移量)
} else {
echo "未找到。<br>";
}
// 查找单个中文 '好'
$char_single = "好";
$pos_strpos_single = strpos($text, $char_single); // 3 (字节偏移)
$pos_mb_strpos_single = mb_strpos($text, $char_single, 0, $encoding); // 1 (字符偏移)
echo "strpos 查找 '{$char_single}':{$pos_strpos_single}<br>";
echo "mb_strpos 查找 '{$char_single}':{$pos_mb_strpos_single}<br>";
// 统计次数
$text_count = "PHP编程,PHP是世界上最好的语言,PHP!";
$count_strpos = substr_count($text_count, "PHP"); // 3
$count_mb_strpos = mb_substr_count($text_count, "PHP", $encoding); // 3
echo "substr_count 统计 'PHP':{$count_strpos}<br>";
echo "mb_substr_count 统计 'PHP':{$count_mb_strpos}<br>";
?>

最佳实践: 如果你的应用需要处理多语言或任何可能包含非ASCII字符的文本,强烈建议始终使用 mb_ 系列函数,并明确指定字符编码(通常是 'UTF-8'),以避免潜在的字符乱码或计算错误。

五、使用正则表达式进行高级查找:preg_match() 与 preg_match_all()

当普通的字符串查找函数无法满足你的需求,例如你需要查找符合特定模式(而不是固定子串)的字符或子串,或者需要进行更复杂的匹配操作时,正则表达式是你的强大武器。PHP提供了PCRE(Perl Compatible Regular Expressions)扩展来处理正则表达式。

5.1 preg_match():查找首次匹配的模式


preg_match() 用于执行一个正则表达式匹配。它会尝试在字符串中查找与给定正则表达式匹配的第一个模式。
语法: preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int|false
参数:

$pattern:要匹配的正则表达式。
$subject:在哪个字符串中进行查找。
$matches(可选):一个数组,用于存储所有匹配到的结果(如果存在捕获组)。
$flags(可选):用于控制匹配行为的标志。
$offset(可选):从 $subject 的哪个位置开始查找。


返回值: 如果找到匹配,返回1;如果没有找到,返回0;如果发生错误,返回 false。

<?php
$text = "My phone number is 138-1234-5678 and home phone is (010)87654321.";
$pattern_phone = '/\d{3}-\d{4}-\d{4}/'; // 匹配手机号码模式
if (preg_match($pattern_phone, $text, $matches)) {
echo "找到了手机号码:" . $matches[0] . "<br>"; // 138-1234-5678
} else {
echo "未找到手机号码。<br>";
}
$pattern_email = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';
$email_text = "Contact us at info@ or support@.";
if (preg_match($pattern_email, $email_text, $email_matches)) {
echo "找到第一个邮箱地址:" . $email_matches[0] . "<br>"; // info@
}
?>

5.2 preg_match_all():查找所有匹配的模式


preg_match_all() 与 preg_match() 类似,但它会查找字符串中所有与正则表达式匹配的模式。
语法: preg_match_all(string $pattern, string $subject, array &$matches, int $flags = PREG_PATTERN_ORDER, int $offset = 0): int|false
参数:

$pattern, $subject, $offset 与 preg_match() 相同。
$matches:一个多维数组,用于存储所有匹配到的结果。其结构由 $flags 参数控制。
$flags(可选):常用的有 PREG_PATTERN_ORDER (默认,每个匹配项一个数组元素) 和 PREG_SET_ORDER (每个匹配集一个数组元素)。


返回值: 找到完整匹配的次数;如果没有找到,返回0;如果发生错误,返回 false。

<?php
$text = "My phone number is 138-1234-5678 and home phone is (010)87654321. Another number is 139-9876-5432.";
$pattern_phone_global = '/\d{3}-\d{4}-\d{4}/';
// 查找所有手机号码
if (preg_match_all($pattern_phone_global, $text, $matches_all)) {
echo "找到所有手机号码:<br>";
foreach ($matches_all[0] as $phone) {
echo $phone . "<br>";
}
} else {
echo "未找到手机号码。<br>";
}
$email_text = "Contact us at info@ or support@. Also admin@.";
$pattern_email_global = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';
if (preg_match_all($pattern_email_global, $email_text, $email_matches_all, PREG_SET_ORDER)) {
echo "找到所有邮箱地址(PREG_SET_ORDER):<br>";
foreach ($email_matches_all as $match) {
echo $match[0] . "<br>";
}
}
?>

正则表达式的优势:
模式匹配: 可以查找复杂的字符序列,而不仅仅是固定的子串。
灵活性: 可以匹配数字、字母、空格、特殊字符的任意组合。
提取数据: 通过捕获组(括号 ()),可以从匹配到的字符串中提取特定部分。

正则表达式的缺点:
性能开销: 相对于简单的字符串函数,正则表达式的解析和匹配通常需要更多的CPU时间。
复杂性: 正则表达式本身可能难以阅读和编写,尤其对于不熟悉它们的开发者。

六、性能考量与最佳实践

选择正确的字符串查找函数不仅关乎功能实现,也影响着代码的性能和可维护性。

6.1 性能对比



简单查找: 对于查找固定的单个字符或子串,strpos()、stripos()、strstr()、substr_count() 等标准字符串函数是最高效的选择。它们是PHP核心的一部分,经过高度优化,且没有正则表达式的解析开销。
多字节查找: 如果处理多字节字符,mb_strpos() 等 mb_ 系列函数是必要的,其性能略低于字节安全函数,但为了正确性牺牲一些性能是值得的。
复杂模式: 只有当需要进行模式匹配时才使用正则表达式(preg_match()、preg_match_all())。正则表达式的性能开销显著,尤其是在处理大型字符串或复杂模式时。

6.2 最佳实践



优先使用简单函数: 如果一个简单的 strpos() 或 strstr() 能够解决问题,就不要使用正则表达式。这不仅性能更好,代码也更易读。
注意严格比较: 使用 strpos() 时,始终使用 !== false 或 === 0 进行严格比较,避免 0 被误判为 false。
考虑字符编码: 如果你的应用需要支持多语言或UTF-8编码,请务必使用 mb_ 系列函数,并明确指定编码,以防止意外行为。
谨慎使用正则表达式: 正则表达式虽然强大,但编写和调试都相对复杂。在编写时,尽量使模式简洁高效,避免不必要的回溯。测试你的正则表达式以确保它们按预期工作。
使用 str_contains() (PHP 8+): 对于仅仅判断字符串是否包含某个子串的情况,PHP 8 引入了 str_contains() 函数,它返回布尔值,语义更清晰,也省去了 !== false 的判断。

<?php
// PHP 8+ 的 str_contains()
if (version_compare(PHP_VERSION, '8.0.0', '>=')) {
$text = "PHP is awesome!";
if (str_contains($text, "awesome")) {
echo "字符串包含 'awesome'。<br>";
} else {
echo "字符串不包含 'awesome'。<br>";
}
}
?>

七、总结

PHP提供了丰富而强大的字符串查找功能,从简单的字符或子串定位,到复杂的模式匹配,几乎可以满足所有场景的需求。理解这些函数的差异、优势和限制,并根据具体情况选择最合适的工具,是每一位PHP开发者必备的技能。
对于区分大小写的首次出现位置,使用 strpos()。
对于不区分大小写的首次出现位置,使用 stripos()。
如果需要获取从匹配点到字符串末尾的部分,使用 strstr() 或 stristr()。
对于查找最后一次出现的位置,使用 strrpos() 或 strripos()。
需要统计子串出现次数,使用 substr_count()。
处理多字节字符(如中文UTF-8),务必使用 mb_ 前缀的函数,如 mb_strpos()。
当需要进行复杂模式匹配或提取时,使用 preg_match() 或 preg_match_all()。
在PHP 8及更高版本中,简单判断子串是否存在,推荐使用 str_contains()。

熟练掌握这些函数,将使你在PHP字符串处理方面如鱼得水,编写出更高效、健壮和可维护的代码。```

2025-10-23


上一篇:PhoStorm PHP 本地开发环境:安装、配置与PHP文件运行实战

下一篇:PHP字符与字符串的深度解析:转换、操作与最佳实践