PHP 字符串中查找字符与子字符串:从基础到高效实践的全面指南65

在日常的编程工作中,字符串处理无疑是最常见的任务之一。无论是用户输入验证、数据解析、内容搜索还是日志分析,我们都经常需要判断一个字符串中是否包含某个特定的字符或子字符串。PHP 作为一门广泛应用于 Web 开发的语言,提供了一系列强大且灵活的函数来应对这些需求。本文将深入探讨 PHP 中检查字符或子字符串是否存在的各种方法,从基础函数到高级技巧,并结合实际应用场景,助您选择最合适的工具。

一、最常用的方法:strpos() 与 stripos()

当谈到在 PHP 字符串中查找字符或子字符串时,strpos() 无疑是许多开发者首先想到的函数。它简单、高效且历史悠久。

1. strpos():大小写敏感的查找


strpos() 函数用于查找字符串在另一个字符串中首次出现的位置(索引)。如果找到了,它会返回该子字符串的起始位置(从 0 开始);如果未找到,则返回布尔值 false。

语法: strpos(string $haystack, string $needle, int $offset = 0): int|false
$haystack:要搜索的字符串。
$needle:要查找的字符或子字符串。
$offset:可选参数,指定从 $haystack 的哪个位置开始搜索。默认为 0。

关键点:返回 0 与 false 的区别

这是使用 strpos() 时最容易出错的地方。如果 $needle 在 $haystack 的开头找到(即索引为 0),strpos() 将返回整数 0。而如果未找到,则返回布尔值 false。由于在 PHP 中,0 是一个“假值”(falsy value),直接使用 if (strpos(...)) 这样的判断会导致逻辑错误。因此,务必使用严格比较运算符 !== false 或 === false。

示例:<?php
$text = "Hello, world! How are you?";
// 查找 "world" (存在)
if (strpos($text, "world") !== false) {
echo "<p>'world' 存在于字符串中。</p>"; // 输出
} else {
echo "<p>'world' 不存在于字符串中。</p>";
}
// 查找 "hello" (大小写敏感,不存在)
if (strpos($text, "hello") !== false) {
echo "<p>'hello' 存在于字符串中。</p>";
} else {
echo "<p>'hello' 不存在于字符串中。</p>"; // 输出
}
// 查找 "Hello" (在开头,返回 0)
if (strpos($text, "Hello") !== false) { // 正确判断
echo "<p>'Hello' 存在于字符串中。其位置是: " . strpos($text, "Hello") . "</p>"; // 输出 'Hello' 存在于字符串中。其位置是: 0
} else {
echo "<p>'Hello' 不存在于字符串中。</p>";
}
// 错误示范:直接使用 if (strpos(...))
if (strpos($text, "Hello")) { // 此处会判断为 false,因为 0 被认为是 false
echo "<p>错误:'Hello' 存在 (尽管其位置是0)。</p>";
} else {
echo "<p>错误:'Hello' 不存在 (因为0被认为是false)。</p>"; // 输出
}
// 从指定位置开始查找
if (strpos($text, "o", 5) !== false) { // 从索引5开始查找 'o',实际在索引6
echo "<p>从索引5开始,'o' 存在于字符串中。其位置是: " . strpos($text, "o", 5) . "</p>"; // 输出位置 6
}
?>

2. stripos():大小写不敏感的查找


stripos() 函数与 strpos() 功能类似,但它在查找时会忽略大小写。这在进行用户输入或非严格内容匹配时非常有用。

语法: stripos(string $haystack, string $needle, int $offset = 0): int|false

示例:<?php
$text = "Hello, world! How are you?";
// 查找 "hello" (大小写不敏感,存在)
if (stripos($text, "hello") !== false) {
echo "<p>'hello' (不区分大小写) 存在于字符串中。</p>"; // 输出
} else {
echo "<p>'hello' 不存在于字符串中。</p>";
}
// 查找 "WORLD" (大小写不敏感,存在)
if (stripos($text, "WORLD") !== false) {
echo "<p>'WORLD' (不区分大小写) 存在于字符串中。</p>"; // 输出
} else {
echo "<p>'WORLD' 不存在于字符串中。</p>";
}
?>

二、PHP 8+ 的新选择:str_contains()

为了解决 strpos() 返回 0 和 false 带来的混淆,并在语义上更加清晰地表达“是否包含”的意图,PHP 8.0 引入了一个新的函数:str_contains()。

str_contains():语义清晰的布尔判断


str_contains() 函数只返回布尔值 true 或 false,它直接回答了“字符串 A 是否包含字符串 B”的问题,避免了对索引 0 的特殊处理。

语法: str_contains(string $haystack, string $needle): bool
$haystack:要搜索的字符串。
$needle:要查找的字符或子字符串。

注意: 此函数是大小写敏感的。

示例:<?php
// 仅适用于 PHP 8.0 及更高版本
$text = "Hello, world! How are you?";
// 查找 "world" (存在)
if (str_contains($text, "world")) {
echo "<p>'world' 存在于字符串中。</p>"; // 输出
} else {
echo "<p>'world' 不存在于字符串中。</p>";
}
// 查找 "hello" (大小写敏感,不存在)
if (str_contains($text, "hello")) {
echo "<p>'hello' 存在于字符串中。</p>";
} else {
echo "<p>'hello' 不存在于字符串中。</p>"; // 输出
}
// 查找 "Hello" (存在)
if (str_contains($text, "Hello")) {
echo "<p>'Hello' 存在于字符串中。</p>"; // 输出
} else {
echo "<p>'Hello' 不存在于字符串中。</p>";
}
// 如果需要大小写不敏感的判断,且正在使用 PHP 8+,可以结合 strtolower()
if (str_contains(strtolower($text), strtolower("Hello"))) {
echo "<p>'Hello' (不区分大小写) 存在于字符串中。</p>"; // 输出
}
?>

推荐: 如果您的项目运行在 PHP 8.0 或更高版本,并且您只需要判断是否存在,str_contains() 是最推荐的选项,因为它代码更简洁,意图更明确。

三、不仅查找,还要获取:strstr() 与 stristr()

有时候,我们不仅需要知道子字符串是否存在,还需要获取从该子字符串开始到原字符串末尾的部分。这时,strstr() 和 stristr() 就派上用场了。

1. strstr():大小写敏感地获取子字符串


strstr() 函数查找 $needle 在 $haystack 中首次出现的位置,并返回从该位置到字符串结尾的所有内容。如果未找到,则返回 false。

语法: strstr(string $haystack, string $needle, bool $before_needle = false): string|false
$haystack:要搜索的字符串。
$needle:要查找的字符或子字符串。
$before_needle:可选参数。如果设置为 true,则返回 $needle 第一次出现之前的字符串部分。默认为 false。

示例:<?php
$email = "user@";
$url = "/manual/zh/";
// 获取邮箱域名部分
$domain = strstr($email, "@"); // 返回 "@"
if ($domain !== false) {
echo "<p>邮箱域名: " . substr($domain, 1) . "</p>"; // 输出 ""
}
// 获取协议部分之前的 URL (使用 $before_needle = true)
$protocol = strstr($url, "://", true); // 返回 "https"
if ($protocol !== false) {
echo "<p>协议: " . $protocol . "</p>"; // 输出 "https"
}
// 查找不存在的子字符串
if (strstr($email, ".org") === false) {
echo "<p>邮箱中不包含 '.org'。</p>"; // 输出
}
?>

2. stristr():大小写不敏感地获取子字符串


stristr() 是 strstr() 的大小写不敏感版本,功能和参数完全相同。

语法: stristr(string $haystack, string $needle, bool $before_needle = false): string|false

示例:<?php
$text = "My Website is ";
// 查找 "website" (不区分大小写),并返回从此处开始的字符串
$result = stristr($text, "website"); // 返回 "Website is "
if ($result !== false) {
echo "<p>从 'website' 开始的部分: " . $result . "</p>"; // 输出
}
// 查找 "EXAMPLE" (不区分大小写),并返回之前的部分
$result_before = stristr($text, "EXAMPLE", true); // 返回 "My Website is "
if ($result_before !== false) {
echo "<p>在 'EXAMPLE' 之前的部分: " . $result_before . "</p>"; // 输出
}
?>

四、强大的模式匹配:preg_match()

当您需要查找的不仅仅是固定的字符或子字符串,而是符合某种模式(例如,数字、字母、特定格式的日期、URL等)的内容时,正则表达式就是您的最佳选择,而 preg_match() 是 PHP 中用于执行正则表达式匹配的主要函数。

preg_match():正则表达式匹配


preg_match() 函数尝试对一个字符串进行正则表达式匹配。如果匹配成功,它返回 1;如果未匹配成功,则返回 0;如果发生错误,则返回 false。

语法: preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int|false
$pattern:要搜索的正则表达式模式。
$subject:要搜索的字符串。
$matches:可选参数,如果指定,则会将所有匹配到的结果存储到此数组中。

示例:<?php
$text = "Today is 2023-10-27, and my email is user@.";
// 查找是否包含任何数字
if (preg_match('/\d/', $text)) {
echo "<p>字符串包含数字。</p>"; // 输出
}
// 查找是否包含特定格式的日期 (YYYY-MM-DD)
if (preg_match('/\d{4}-\d{2}-\d{2}/', $text)) {
echo "<p>字符串包含日期格式 'YYYY-MM-DD'。</p>"; // 输出
}
// 查找是否包含 email 地址,并提取
if (preg_match('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', $text, $matches)) {
echo "<p>字符串包含邮箱地址: " . $matches[0] . "</p>"; // 输出 'user@'
}
// 查找 "world" (大小写不敏感,使用 /i 修正符)
if (preg_match('/world/i', $text)) {
echo "<p>'world' (不区分大小写) 存在于字符串中。</p>"; // 不会输出,因为 $text 中没有 "world"
}
$text2 = "Hello World!";
if (preg_match('/world/i', $text2)) {
echo "<p>'world' (不区分大小写) 存在于字符串中。</p>"; // 输出
}
?>

何时使用 preg_match():
当需要进行复杂的模式匹配时,例如验证格式、提取特定类型的数据。
需要大小写不敏感的匹配,但又要利用正则表达式的其他特性时(如 /i 修正符)。

性能考量: 正则表达式通常比简单的字符串函数开销更大。如果简单的 strpos() 或 str_contains() 能够满足需求,应优先使用它们。

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

如果您的需求是统计一个子字符串在另一个字符串中出现的次数,而不是仅仅判断是否存在,那么 substr_count() 是最直接高效的方法。

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


substr_count() 函数返回 $needle 在 $haystack 中出现的次数。它也是大小写敏感的。

语法: substr_count(string $haystack, string $needle, int $offset = 0, ?int $length = null): int
$haystack:要搜索的字符串。
$needle:要查找的字符或子字符串。
$offset:可选参数,指定从 $haystack 的哪个位置开始计数。
$length:可选参数,指定在 $haystack 中搜索的长度限制。

示例:<?php
$text = "banana is a yellow fruit, I like banana.";
// 统计 "banana" 出现的次数
$count = substr_count($text, "banana"); // 返回 2
echo "<p>'banana' 出现了 " . $count . " 次。</p>"; // 输出
// 统计 "a" 出现的次数
$count_a = substr_count($text, "a"); // 返回 8 (所有 'a' 的总数)
echo "<p>'a' 出现了 " . $count_a . " 次。</p>"; // 输出
// 统计从指定位置开始的次数
$text2 = "ababab";
$count_ab = substr_count($text2, "ab", 1); // 从索引1开始计数 'ab',返回 2
echo "<p>'ab' 从索引1开始出现了 " . $count_ab . " 次。</p>"; // 输出
?>

注意: substr_count() 不支持重叠匹配。例如,substr_count("aaaaa", "aaa") 返回 1,而不是 3。

六、处理多字节字符:mb_* 系列函数

上述所有函数(除了 preg_match() 在某些情况下需要特殊处理外)都是字节安全的,这意味着它们会按字节而不是按字符进行操作。对于包含非 ASCII 字符(如中文、日文、表情符号等)的多字节字符串,直接使用 strpos() 等函数可能会得到错误的结果,因为一个字符可能由多个字节组成。

为了正确处理多字节字符串,PHP 提供了多字节字符串(MultiByte String)函数库,通常以 mb_ 为前缀。

mb_strpos()、mb_stripos()、mb_strstr()、mb_stristr() 等


这些函数在功能上与它们的非 mb_ 版本类似,但它们接受一个额外的 $encoding 参数,用于指定字符串的字符编码(如 'UTF-8')。

示例:<?php
$multibyte_text = "你好世界,Hello World!";
$encoding = 'UTF-8';
// 使用 strpos() 查找 '世' (可能出错或返回非预期结果)
// var_dump(strpos($multibyte_text, "世")); // 返回 6 (字节位置,而不是字符位置)
// 使用 mb_strpos() 查找 '世' (正确)
if (mb_strpos($multibyte_text, "世", 0, $encoding) !== false) {
echo "<p>'世' 存在于字符串中。其字符位置是: " . mb_strpos($multibyte_text, "世", 0, $encoding) . "</p>"; // 输出 2
}
// 使用 mb_str_contains() (PHP 8+ 且需要自定义函数,因为PHP本身没有mb_str_contains)
// 如果需要一个多字节安全的 str_contains 行为,可以这样实现:
function mb_str_contains_safe(string $haystack, string $needle, string $encoding = 'UTF-8'): bool {
return mb_strpos($haystack, $needle, 0, $encoding) !== false;
}
if (mb_str_contains_safe($multibyte_text, "世界")) {
echo "<p>'世界' (多字节安全) 存在于字符串中。</p>"; // 输出
}
// mb_strstr() 示例
$result = mb_strstr($multibyte_text, "世界", false, $encoding);
if ($result !== false) {
echo "<p>从 '世界' 开始的部分 (多字节安全): " . $result . "</p>"; // 输出 "世界,Hello World!"
}
?>

最佳实践: 如果您的应用程序需要处理用户输入、国际化内容或任何可能包含多字节字符的字符串,强烈建议始终使用 mb_* 系列函数,并明确指定字符编码(通常是 'UTF-8'),以避免潜在的字符乱码或错误判断问题。

七、性能考量与选择建议

在大多数 Web 应用中,字符串查找的性能差异微乎其微,不值得进行微优化。但了解不同函数之间的性能特性,有助于在处理大量数据或性能敏感场景时做出明智选择。
str_contains() (PHP 8+): 最快,因为它只返回布尔值,并且是专门为此目的设计的。
strpos() / stripos(): 非常快,仅次于 str_contains()。当需要知道位置时,是最佳选择。
strstr() / stristr(): 稍慢于 strpos(),因为它可能需要复制并返回字符串的一部分。
substr_count(): 效率较高,专门用于计数。
preg_match(): 通常最慢,因为正则表达式引擎需要解析模式、构建状态机等。只有在模式复杂到无法用简单字符串函数表达时才应使用。
mb_* 系列函数: 由于需要处理多字节字符的解码和编码,通常会比非 mb_ 版本稍慢,但为了正确性,在多字节环境下是必须的。

选择建议:
PHP 8.0+ 且只判断是否存在: str_contains($haystack, $needle)。简洁、清晰、高效。
PHP < 8.0 且只判断是否存在: strpos($haystack, $needle) !== false。注意严格比较。
需要查找位置: strpos() (大小写敏感) 或 stripos() (大小写不敏感)。
需要获取从子字符串开始的部分: strstr() (大小写敏感) 或 stristr() (大小写不敏感)。
需要统计出现次数: substr_count()。
需要复杂模式匹配、验证格式或提取特定数据: preg_match()。
处理包含非 ASCII 字符的字符串(如中文、UTF-8编码): 始终优先使用 mb_strpos()、mb_strstr() 等 mb_* 系列函数,并指定编码。

八、总结

PHP 为在字符串中查找字符或子字符串提供了丰富而多样的工具。从基本的 strpos() 到 PHP 8+ 中更语义化的 str_contains(),再到强大的正则表达式 preg_match(),以及针对多字节字符的 mb_* 系列函数,每种方法都有其最佳应用场景。作为一名专业的程序员,理解这些函数的特性、优缺点以及何时使用它们至关重要。正确地选择和使用这些字符串处理函数,不仅能让您的代码更加健壮和高效,也能有效避免潜在的字符编码问题,从而提升应用程序的整体质量。

2025-10-22


上一篇:PHP 文件路径深度解析:获取真实、规范化路径的最佳实践

下一篇:PHP 分批获取数据:高效处理海量数据的策略与实践