PHP 字符串长度获取与安全截取:strlen, mb_strlen, substr, mb_substr 全面指南386


在 PHP 开发中,字符串操作是日常工作中不可或缺的一部分。无论是获取字符串的长度以进行校验,还是截取特定部分的字符串用于显示摘要,这些都是基础且核心的功能。然而,对于初学者乃至经验丰富的开发者来说,处理字符串长度和截取时,尤其是涉及到多字节字符集(如 UTF-8)时,往往会遇到意想不到的问题,例如中文乱码、字符计数不准确等。本文将深入探讨 PHP 中用于字符串长度获取和截取的关键函数:`strlen()`、`mb_strlen()`、`substr()` 和 `mb_substr()`,帮助您彻底理解它们的工作原理、适用场景以及如何安全、高效地处理各种字符编码。

一、理解字符串与字符编码

在深入学习函数之前,我们首先需要理解一个核心概念:字符编码。计算机存储的都是二进制数据,字符(如 'A', '你', '€')需要通过特定的编码规则转换为二进制才能被存储和传输。常见的编码有 ASCII、ISO-8859-1 (Latin-1) 和 UTF-8 等。
ASCII / Latin-1: 它们是单字节编码,一个字符占用一个字节。这意味着一个字符的“长度”和它所占的“字节数”是相等的。
UTF-8: 这是一种变长多字节编码。一个英文字符可能占用 1 个字节,一个中文字符通常占用 3 个字节,而一些特殊符号可能占用更多。这意味着在 UTF-8 编码下,一个字符的“长度”(即字符个数)和它所占的“字节数”是不同的。忽略这一点是导致字符串处理问题的主要原因。

PHP 字符串在内部存储时并不强制指定编码,它只是一个字节序列。因此,当执行字符串操作时,函数是按字节处理还是按字符处理,以及如何解释这些字节序列,就变得至关重要。

二、获取字符串长度:strlen() vs mb_strlen()

获取字符串长度是字符串操作中最基本的需求。PHP 提供了两个主要的函数来完成这项任务,但它们的工作方式截然不同。

2.1 strlen():字节长度的忠实计量者


strlen() 函数返回字符串的字节长度。它的语法非常简单:
int strlen ( string $string )

特点:
始终计算字符串中的字节数。
对于单字节编码(如 ASCII, Latin-1),其返回值等于字符数。
对于多字节编码(如 UTF-8),其返回值不等于字符数,而是每个字符所占字节的总和。

示例:
<?php
$ascii_string = "Hello World!";
$utf8_string_en = "PHP rocks!";
$utf8_string_cn = "你好世界!"; // 每个中文字符占3个字节
$utf8_string_mix = "Hello 你好!"; // 'H', 'e', 'l', 'l', 'o', ' ', '你', '好', '!'
echo "ASCII字符串: '" . $ascii_string . "'";
echo "strlen(): " . strlen($ascii_string) . " (正确,12个字符)";
echo "UTF-8英文字符串: '" . $utf8_string_en . "'";
echo "strlen(): " . strlen($utf8_string_en) . " (正确,10个字符)";
echo "UTF-8中文字符串: '" . $utf8_string_cn . "'"; // 你(3) 好(3) 世(3) 界(3) !(3)
echo "strlen(): " . strlen($utf8_string_cn) . " (错误,实际是15字节,但只有5个字符)";
echo "UTF-8混合字符串: '" . $utf8_string_mix . "'"; // H(1) e(1) l(1) l(1) o(1) 空格(1) 你(3) 好(3) !(1)
echo "strlen(): " . strlen($utf8_string_mix) . " (错误,实际是13字节,但只有8个字符)";
?>

输出:
ASCII字符串: 'Hello World!'
strlen(): 12 (正确,12个字符)
UTF-8英文字符串: 'PHP rocks!'
strlen(): 10 (正确,10个字符)
UTF-8中文字符串: '你好世界!'
strlen(): 15 (错误,实际是15字节,但只有5个字符)
UTF-8混合字符串: 'Hello 你好!'
strlen(): 13 (错误,实际是13字节,但只有8个字符)

当您的应用程序主要处理单字节编码的字符串,或者您确实需要知道字符串的字节大小时,strlen() 是合适的。但对于包含多字节字符(如中文、日文、韩文、表情符号等)的 UTF-8 字符串,使用 strlen() 来获取字符个数将导致错误的结果。

2.2 mb_strlen():多字节字符的守护神


mb_strlen() 是 PHP mbstring 扩展提供的一个函数,它专门用于处理多字节字符串。它返回字符串的字符个数,而非字节数。
int mb_strlen ( string $string [, string $encoding = mb_internal_encoding() ] )

特点:
计算字符串中的字符数。
需要指定或通过 mb_internal_encoding() 设置正确的字符编码,否则可能导致错误或默认使用不合适的编码。
是处理 UTF-8 等多字节编码字符串的首选函数。
要求: mbstring 扩展必须启用。在大多数现代 PHP 环境中,它默认是启用的。如果未启用,您需要在 中取消注释 extension=mbstring。

示例:
<?php
// 设置内部编码,推荐在应用程序入口处设置
mb_internal_encoding("UTF-8");
$ascii_string = "Hello World!";
$utf8_string_en = "PHP rocks!";
$utf8_string_cn = "你好世界!";
$utf8_string_mix = "Hello 你好!";
echo "ASCII字符串: '" . $ascii_string . "'";
echo "mb_strlen(): " . mb_strlen($ascii_string) . " (正确,12个字符)";
echo "UTF-8英文字符串: '" . $utf8_string_en . "'";
echo "mb_strlen(): " . mb_strlen($utf8_string_en) . " (正确,10个字符)";
echo "UTF-8中文字符串: '" . $utf8_string_cn . "'";
echo "mb_strlen(): " . mb_strlen($utf8_string_cn) . " (正确,5个字符)";
echo "UTF-8混合字符串: '" . $utf8_string_mix . "'";
echo "mb_strlen(): " . mb_strlen($utf8_string_mix) . " (正确,8个字符)";
// 也可以直接指定编码,覆盖 mb_internal_encoding() 设置
echo "指定编码的 mb_strlen(): " . mb_strlen("这是一个测试", "UTF-8") . "";
?>

输出:
ASCII字符串: 'Hello World!'
mb_strlen(): 12 (正确,12个字符)
UTF-8英文字符串: 'PHP rocks!'
mb_strlen(): 10 (正确,10个字符)
UTF-8中文字符串: '你好世界!'
mb_strlen(): 5 (正确,5个字符)
UTF-8混合字符串: 'Hello 你好!'
mb_strlen(): 8 (正确,8个字符)
指定编码的 mb_strlen(): 6

对于任何需要获取字符串实际字符个数的场景,尤其是在处理用户输入、文件名、数据库内容等可能包含多字节字符的数据时,强烈推荐使用 mb_strlen()。它能够确保您得到的是准确的字符计数,避免因编码问题导致的逻辑错误。

2.3 长度获取的最佳实践


在现代 Web 开发中,UTF-8 几乎是默认的编码标准。因此,以下是关于字符串长度获取的最佳实践:
始终使用 mb_strlen():除非您有非常明确的理由和确凿的证据表明您的字符串只包含 ASCII 字符且需要字节长度,否则请一律使用 mb_strlen()。
设置内部编码:在您的应用程序入口文件(如 或框架的引导文件)中,设置 mb_internal_encoding("UTF-8");。这将为所有未显式指定编码的 mb_* 函数提供默认编码。
理解 :您也可以在 中设置 mbstring.internal_encoding = UTF-8 和 mbstring.func_overload = 0(避免函数重载导致混淆)。

三、截取字符串:substr() vs mb_substr()

字符串截取是另一个常见的操作,用于从字符串中提取特定部分。与获取长度类似,PHP 也提供了两个主要函数,分别按字节和按字符进行截取。

3.1 substr():基于字节的截取操作


substr() 函数返回字符串的部分,该部分由 start 和 length 参数指定。它也是按字节进行操作的。
string substr ( string $string , int $start [, int $length ] )

参数说明:
$string:要截取的字符串。
$start:开始位置。

正数:从字符串开头计算(第一个字符为 0)。
负数:从字符串结尾计算(-1 代表最后一个字符)。


$length(可选):截取长度。

正数:截取指定长度的字节。
负数:从字符串结尾向前计算,截取到该位置(例如 -1 意味着截取到倒数第二个字节)。
省略:截取从 start 位置到字符串末尾的所有内容。



特点:
始终按字节进行截取。
对于单字节编码字符串,其行为符合预期。
对于多字节编码字符串,如果截取位置恰好将一个多字节字符“切开”,将导致乱码。

示例:
<?php
$ascii_string = "Hello World!";
$utf8_string_cn = "你好世界!"; // 每个中文字符占3个字节
echo "ASCII字符串: '" . $ascii_string . "'";
echo "substr(0, 5): '" . substr($ascii_string, 0, 5) . "'"; // "Hello"
echo "substr(6): '" . substr($ascii_string, 6) . "'"; // "World!"
echo "substr(-6): '" . substr($ascii_string, -6) . "'"; // "World!"
echo "UTF-8中文字符串: '" . $utf8_string_cn . "'";
// 尝试截取第一个字符('你' 占3个字节)
echo "substr(0, 1): '" . substr($utf8_string_cn, 0, 1) . "' (乱码!因为它只截取了'你'的第一个字节)";
echo "substr(0, 3): '" . substr($utf8_string_cn, 0, 3) . "' (正确,截取了'你')";
echo "substr(3, 3): '" . substr($utf8_string_cn, 3, 3) . "' (正确,截取了'好')";
echo "substr(0, 5): '" . substr($utf8_string_cn, 0, 5) . "' (乱码!'你'占3字节,'好'占2字节,被切开)";
?>

输出:
ASCII字符串: 'Hello World!'
substr(0, 5): 'Hello'
substr(6): 'World!'
substr(-6): 'World!'
UTF-8中文字符串: '你好世界!'
substr(0, 1): '�' (乱码!因为它只截取了'你'的第一个字节)
substr(0, 3): '你' (正确,截取了'你')
substr(3, 3): '好' (正确,截取了'好')
substr(0, 5): '你好�' (乱码!'你'占3字节,'好'占2字节,被切开)

与 strlen() 类似,substr() 在处理多字节编码字符串时极易出错,因为它不理解字符边界,只知道字节。如果您的应用程序面向全球用户或处理包含多字节字符的数据,请不要使用 substr() 进行字符层面的截取。

3.2 mb_substr():多字节字符的智能截取


mb_substr() 是 mbstring 扩展提供的多字节字符串截取函数。它按字符数进行截取,确保在多字节编码下不会产生乱码。
string mb_substr ( string $string , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] )

参数说明:
$string:要截取的字符串。
$start:开始位置(字符偏移量)。

正数:从字符串开头计算(第一个字符为 0)。
负数:从字符串结尾计算(-1 代表最后一个字符)。


$length(可选):截取长度(字符个数)。

正数:截取指定长度的字符。
负数:从字符串结尾向前计算,截取到该位置。
省略:截取从 start 位置到字符串末尾的所有内容。


$encoding(可选):字符串的字符编码。如果省略,则使用 mb_internal_encoding() 的值。

特点:
始终按字符进行截取,不会切开多字节字符。
需要指定或设置正确的字符编码。
是处理 UTF-8 等多字节编码字符串的首选函数。
要求: mbstring 扩展必须启用。

示例:
<?php
mb_internal_encoding("UTF-8"); // 确保设置内部编码
$ascii_string = "Hello World!";
$utf8_string_cn = "你好世界!";
$utf8_string_mix = "Hello 你好!";
echo "ASCII字符串: '" . $ascii_string . "'";
echo "mb_substr(0, 5): '" . mb_substr($ascii_string, 0, 5) . "'"; // "Hello"
echo "mb_substr(6): '" . mb_substr($ascii_string, 6) . "'"; // "World!"
echo "mb_substr(-6): '" . mb_substr($ascii_string, -6) . "'"; // "World!"
echo "UTF-8中文字符串: '" . $utf8_string_cn . "'";
echo "mb_substr(0, 1): '" . mb_substr($utf8_string_cn, 0, 1) . "' (正确,截取'你')";
echo "mb_substr(1, 1): '" . mb_substr($utf8_string_cn, 1, 1) . "' (正确,截取'好')";
echo "mb_substr(0, 3): '" . mb_substr($utf8_string_cn, 0, 3) . "' (正确,截取'你好世')";
echo "mb_substr(-2): '" . mb_substr($utf8_string_cn, -2) . "' (正确,截取'世界!')";
echo "UTF-8混合字符串: '" . $utf8_string_mix . "'";
echo "mb_substr(0, 5): '" . mb_substr($utf8_string_mix, 0, 5) . "' (正确,截取'Hello')";
echo "mb_substr(6, 2): '" . mb_substr($utf8_string_mix, 6, 2) . "' (正确,截取'你好')";
echo "mb_substr(6): '" . mb_substr($utf8_string_mix, 6) . "' (正确,截取'你好!')";
?>

输出:
ASCII字符串: 'Hello World!'
mb_substr(0, 5): 'Hello'
mb_substr(6): 'World!'
mb_substr(-6): 'World!'
UTF-8中文字符串: '你好世界!'
mb_substr(0, 1): '你' (正确,截取'你')
mb_substr(1, 1): '好' (正确,截取'好')
mb_substr(0, 3): '你好世' (正确,截取'你好世')
mb_substr(-2): '世界!' (正确,截取'世界!')
UTF-8混合字符串: 'Hello 你好!'
mb_substr(0, 5): 'Hello' (正确,截取'Hello')
mb_substr(6, 2): '你好' (正确,截取'你好')
mb_substr(6): '你好!' (正确,截取'你好!')

毫无疑问,mb_substr() 是处理多字节编码字符串截取的正确选择。它能够确保您得到预期长度的字符,并且不会产生乱码,这对于用户体验和数据完整性至关重要。

3.3 字符串截取的最佳实践


总结字符串截取的最佳实践,与获取长度类似:
始终使用 mb_substr():这是处理包含多字节字符的字符串截取的唯一安全、可靠的方式。
配合 mb_strlen() 使用:在进行截取操作前,通常需要先获取字符串的实际字符长度,以避免越界或进行条件判断。例如,截取博客文章摘要时,您可能需要确保截取长度不超过文章总长度。
设置内部编码:再次强调在应用程序入口处设置 mb_internal_encoding("UTF-8"); 的重要性。
处理超出长度的情况:当截取长度大于字符串实际长度时,mb_substr() 会返回整个字符串,而不会报错。这通常是期望的行为,但您可以根据业务逻辑进行额外的判断和处理(例如添加省略号)。

四、综合应用与高级考量

了解了这些基础函数后,我们来看一些更实际的应用场景和高级考量。

4.1 安全截取与添加省略号


在许多场景中,如新闻标题、文章摘要等,我们需要将过长的字符串截断,并在末尾添加省略号(...)。这需要结合 mb_strlen() 和 mb_substr() 来完成。
<?php
mb_internal_encoding("UTF-8");
function truncateString(string $text, int $maxLength, string $suffix = '...'): string
{
if ($maxLength <= 0) {
return ''; // 无法截取或截取长度不合法
}
if (mb_strlen($text) > $maxLength) {
// 如果截取长度小于等于后缀长度,那么只显示后缀,或者抛出错误,视业务需求而定
if ($maxLength <= mb_strlen($suffix)) {
return mb_substr($text, 0, $maxLength); // 无法添加省略号,直接截取
}
return mb_substr($text, 0, $maxLength - mb_strlen($suffix)) . $suffix;
}
return $text;
}
$long_text_cn = "PHP 是一种广泛使用的通用目的脚本语言,特别适用于 Web 开发,可以嵌入到 HTML 中。";
$long_text_en = "PHP is a popular general-purpose scripting language that is especially suited to web development.";
$short_text = "短文本";
echo "中文长文本 (30字符限制): " . truncateString($long_text_cn, 30) . "";
echo "中文长文本 (10字符限制): " . truncateString($long_text_cn, 10) . "";
echo "英文长文本 (30字符限制): " . truncateString($long_text_en, 30) . "";
echo "短文本 (30字符限制): " . truncateString($short_text, 30) . "";
echo "短文本 (3字符限制): " . truncateString($short_text, 3) . "";
echo "短文本 (2字符限制,后缀长度为3): " . truncateString($short_text, 2) . ""; // 由于无法添加省略号,直接截取
?>

输出:
中文长文本 (30字符限制): PHP 是一种广泛使用的通用目的脚本语言,特别适用于 Web 开发,可以...
中文长文本 (10字符限制): PHP 是一种广泛...
英文长文本 (30字符限制): PHP is a popular general-purp...
短文本 (30字符限制): 短文本
短文本 (3字符限制): 短文本
短文本 (2字符限制,后缀长度为3): 短文

这个 truncateString 函数考虑了字符串长度、截取限制以及省略号的长度,提供了一个健壮的解决方案。

4.2 mbstring 扩展的配置


为了确保 mb_* 函数的正常和高效工作,mbstring 扩展在 中有一些重要的配置项:
extension=mbstring:确保此行没有被注释,以启用扩展。
mbstring.internal_encoding = UTF-8:推荐设置,这将作为所有 mb_* 函数的默认编码。
mbstring.func_overload = 0:这是一个非常重要的设置。如果设置为非零值,它会导致 strlen()、substr() 等非 mb_* 函数被其 mb_* 版本所替换。虽然这可能看起来方便,但它会导致代码行为不确定,特别是当您依赖字节操作时。强烈建议将其设置为 0,并始终显式使用 mb_* 函数。

在运行时,您也可以使用 mb_internal_encoding() 和 mb_language() 来动态设置编码。

4.3 性能考量


通常情况下,mb_* 函数由于需要解析多字节字符编码,会比对应的单字节函数(如 strlen())略慢。然而,对于大多数 Web 应用程序而言,这种性能差异微乎其微,不足以成为瓶颈。在字符串处理中,正确性远比微小的性能提升更为重要。只有在经过严格的性能分析(profiling)后,确认字符串操作是主要的性能瓶颈时,才需要考虑优化方案,但这通常不会是简单地从 mb_strlen() 切换回 strlen()。

五、总结与建议

通过本文的详细介绍,我们可以得出以下关键结论和建议:
理解字符编码是核心: 在 PHP 中处理字符串,最重要的是要理解字符串的内部编码以及不同编码对长度和截取的影响。
拥抱 UTF-8 和 mbstring: 现代 Web 开发几乎无一例外地使用 UTF-8 编码。为了确保应用程序的健壮性和国际化能力,请始终使用 mbstring 扩展提供的 mb_* 函数。
`mb_strlen()` 用于获取字符长度: 凡是需要知道字符串实际字符个数的场景,请使用 mb_strlen($string, 'UTF-8') 或确保 mb_internal_encoding("UTF-8") 已设置。
`mb_substr()` 用于安全截取: 凡是需要截取字符串的场景,请使用 mb_substr($string, $start, $length, 'UTF-8') 或确保 mb_internal_encoding("UTF-8") 已设置,以避免乱码。
统一编码设置: 在应用程序的引导阶段设置 mb_internal_encoding("UTF-8"); 是一个良好的实践,可以减少编码相关的错误。
避免 `mbstring.func_overload`: 将此配置项设置为 0,并习惯性地使用 mb_* 函数,保持代码行为的一致性和可预测性。

掌握了 strlen()、mb_strlen()、substr() 和 mb_substr() 的正确用法,您就能自信地在 PHP 中处理各种字符串操作,构建出更加稳定、国际友好的应用程序。

2025-10-17


上一篇:PHP 连接与查询 MySQL 数据库:高效数据展示与安全实践指南

下一篇:PHP readdir 深度解析:高效获取文件后缀与目录遍历最佳实践