Skip to content

多字节字符串 mbstring

概述

mbstring 扩展是 PHP 处理多字节字符编码(如 UTF-8、GBK、Big5 等)的核心工具。PHP 原生字符串函数按字节操作,对中文、日文、韩文等多字节字符会产生错误结果。mbstring 提供了对应的 mb_* 系列函数,按字符而非字节处理字符串。

基础概念

为什么需要 mbstring

UTF-8 编码中,不同字符占用的字节数不同:

字符类型字节数示例
ASCII 字母/数字1 字节A (0x41)
拉丁扩展2 字节é (0xC3 0xA9)
中文/日文/韩文3 字节 (0xE4 0xBD 0xA0)
Emoji 表情4 字节😀 (0xF0 0x9F 0x98 0x80)

原生函数 vs mbstring 函数对照表

操作原生函数(字节)mbstring 函数(字符)
长度strlenmb_strlen
截取substrmb_substr
查找位置strposmb_strpos
最后查找strrposmb_strrpos
转小写strtolowermb_strtolower
转大写strtouppermb_strtoupper
首字母大写ucfirstmb_convert_case + MB_CASE_TITLE
子串出现次数substr_countmb_substr_count
发送邮件mailmb_send_mail

语法与代码

mb_strlen — 获取字符数

php
<?php

declare(strict_types=1);

// mb_strlen(string $str, ?string $encoding = null): int
// 返回字符串的字符数(而非字节数)

echo strlen('Hello');           // 5(字节长度 = 字符长度)
echo mb_strlen('Hello');       // 5(字符长度)

echo strlen('你好世界');        // 12(4 个中文 x 3 字节)
echo mb_strlen('你好世界');    // 4(字符长度)

// 指定编码
echo mb_strlen('你好', 'UTF-8');    // 2
echo mb_strlen('你好', 'GBK');      // 2

// 使用 mb_internal_encoding 的默认编码
mb_internal_encoding('UTF-8');
echo mb_strlen('你好世界');    // 4

mb_substr — 按字符截取

php
<?php

declare(strict_types=1);

// mb_substr(string $str, int $start, ?int $length, ?string $encoding): string

$str = '你好,世界!Hello!';

echo mb_substr($str, 0, 2);           // 你好
echo mb_substr($str, 3, 2);           // 世界
echo mb_substr($str, -6);             // Hello!
echo mb_substr($str, -6, 5);           // Hello

// 获取第一个字符
echo mb_substr('你好世界', 0, 1);      // 你

// 获取最后三个字符
echo mb_substr('你好世界', -3);        // 好世界

// 原生 substr 在多字节字符串上的错误结果
echo substr('你好世界', 0, 2);         // (乱码:只截取了部分字节)

mb_strpos — 按字符查找

php
<?php

declare(strict_types=1);

// mb_strpos(string $haystack, string $needle, int $offset, ?string $encoding): int|false

$str = '你好世界,Hello World';

echo mb_strpos($str, '世界');          // 2(字符位置)
echo mb_strpos($str, 'World');         // 8

echo mb_strrpos($str, '你');          // 0(最后出现位置)

// 使用偏移量
echo mb_strpos($str, 'o', 3);          // 10(从第 3 个字符开始查找)

// mb_strpos 同样返回 false 或位置,需要严格比较
$pos = mb_strpos($str, 'PHP');
if ($pos !== false) {
    echo "在位置 {$pos} 找到";
}

mb_strtolower / mb_strtoupper — 大小写转换

php
<?php

declare(strict_types=1);

// 多字节安全的大小写转换
echo mb_strtoupper('hello world');             // HELLO WORLD
echo mb_strtolower('HELLO WORLD');             // hello world

// 对特殊字符的支持(原生函数不支持)
echo strtoupper('déjà vu');                    // Déjà VU(部分字符不转换)
echo mb_strtoupper('déjà vu', 'UTF-8');         // Déjà VU -> 正确: Déjà VU

echo mb_strtolower('CAFÉ', 'UTF-8');           // café
echo mb_strtoupper('café', 'UTF-8');           // CAFé -> 正确: CAFé

// mb_convert_case - 更灵活的大小写转换
echo mb_convert_case('hello world', MB_CASE_UPPER, 'UTF-8');      // HELLO WORLD
echo mb_convert_case('HELLO WORLD', MB_CASE_LOWER, 'UTF-8');      // hello world
echo mb_convert_case('hello world', MB_CASE_TITLE, 'UTF-8');      // Hello World

// MB_CASE_TITLE 对多字节字符的支持
echo mb_convert_case('hello世界', MB_CASE_TITLE, 'UTF-8');        // Hello世界

mb_substr_count — 子串计数

php
<?php

declare(strict_types=1);

// mb_substr_count — 统计子串出现次数(多字节安全)

$str = '你好你好,你好吗?';

echo mb_substr_count($str, '你好');       // 3
echo mb_substr_count($str, '?');         // 1
echo mb_substr_count($str, '你好吗');      // 1

mb_internal_encoding — 设置默认编码

php
<?php

declare(strict_types=1);

// mb_internal_encoding — 设置/获取 mbstring 默认内部编码

// 获取当前编码
echo mb_internal_encoding();  // 例如: UTF-8

// 设置默认编码
mb_internal_encoding('UTF-8');

// 设置后,mb_* 函数可以省略编码参数
echo mb_strlen('你好世界');    // 4(使用 UTF-8)

// 通过 php.ini 设置
// mbstring.internal_encoding = UTF-8

// 检测字符串编码
$encoding = mb_detect_encoding($str, ['UTF-8', 'GBK', 'Big5', 'SJIS']);
echo $encoding;  // 例如: UTF-8

// 编码转换
$gbk = mb_convert_encoding('你好', 'GBK', 'UTF-8');
$utf8 = mb_convert_encoding($gbk, 'UTF-8', 'GBK');

详细说明

编码检测与转换

php
<?php

declare(strict_types=1);

// mb_detect_encoding — 检测字符串编码
// 注意:自动检测不完全可靠,优先指定编码
$enc = mb_detect_encoding($str, 'UTF-8, GBK, Big5, SJIS, ISO-8859-1');

// mb_convert_encoding — 编码转换
$utf8 = mb_convert_encoding($gbkStr, 'UTF-8', 'GBK');
$utf8 = mb_convert_encoding($anyStr, 'UTF-8', 'auto');

// mb_list_encodings — 列出支持的编码
$encodings = mb_list_encodings();
// ['UTF-8', 'UTF-7', 'ASCII', 'EUC-JP', 'SJIS', ...]

HTTP 输入输出编码设置

php
<?php

declare(strict_types=1);

// 设置 HTTP 输入编码
mb_http_input('UTF-8');

// 设置 HTTP 输出编码
mb_http_output('UTF-8');

// 设置内部编码
mb_internal_encoding('UTF-8');

// 使用 mb_convert_variables 同时转换多个变量的编码
mb_convert_variables('UTF-8', 'GBK', $var1, $var2, $var3);

// 在 php.ini 中推荐配置
// mbstring.language = Chinese
// mbstring.internal_encoding = UTF-8
// mbstring.http_input = UTF-8
// mbstring.http_output = UTF-8

mbstring 正则函数

php
<?php

declare(strict_types=1);

// mb_ereg 系列函数 — 多字节正则表达式

// mb_ereg — 正则匹配
if (mb_ereg('[\p{Han}]', '你好World')) {
    echo '包含中文字符';
}

// mb_eregi — 不区分大小写的正则匹配
if (mb_eregi('hello', '你好HELLO世界')) {
    echo '匹配成功';
}

// mb_ereg_replace — 正则替换
$result = mb_ereg_replace('[\s]+', ' ', '你好  世界');  // 你好 世界

// mb_split — 按正则分割
$parts = mb_split('[,,]', '你好,世界,Hello');
// ['你好', '世界', 'Hello']

实战示例

中文字符串截断(不截断字符)

php
<?php

declare(strict_types=1);

function truncateChinese(string $str, int $maxLength, string $suffix = '...'): string
{
    if (mb_strlen($str, 'UTF-8') <= $maxLength) {
        return $str;
    }

    return mb_substr($str, 0, $maxLength - mb_strlen($suffix, 'UTF-8'), 'UTF-8') . $suffix;
}

echo truncateChinese('这是一段很长的中文字符串', 10);
// 输出: 这是一段很长的中文...

echo truncateChinese('短文本', 10);
// 输出: 短文本(不超过长度,不截断)

中文字符串关键词高亮

php
<?php

declare(strict_types=1);

function highlightKeywords(string $text, array $keywords): string
{
    foreach ($keywords as $keyword) {
        if (mb_strlen($keyword, 'UTF-8') === 0) {
            continue;
        }

        $text = mb_ereg_replace(
            preg_quote($keyword, '/'),
            '<mark>\0</mark>',
            $text
        );
    }

    return $text;
}

echo highlightKeywords('PHP是一种流行的编程语言', ['PHP', '编程语言']);
// 输出: <mark>PHP</mark>是一种流行的<mark>编程语言</mark>

中文验证(姓名、手机号)

php
<?php

declare(strict_types=1);

function isChineseName(string $name): bool
{
    // 2-4 个中文字符
    return (bool)preg_match('/^[\x{4e00}-\x{9fa5}]{2,4}$/u', $name);
}

function isChineseText(string $text): bool
{
    // 包含至少一个中文字符
    return (bool)preg_match('/[\p{Han}]/u', $text);
}

function containsOnlyChinese(string $str): bool
{
    // 仅包含中文字符
    return (bool)preg_match('/^[\p{Han}]+$/u', $str);
}

echo isChineseName('张三');      // true
echo isChineseName('张');        // false(少于 2 个字符)
echo isChineseText('Hello世界'); // true
echo isChineseText('Hello');     // false

多语言字符串统计

php
<?php

declare(strict_types=1);

function analyzeString(string $str): array
{
    $chinese = preg_match_all('/[\p{Han}]/u', $str);
    $english = preg_match_all('/[a-zA-Z]/', $str);
    $digits = preg_match_all('/[0-9]/', $str);
    $spaces = preg_match_all('/\s/', $str);
    $punctuation = preg_match_all('/[^\w\s]/u', $str);

    return [
        'total_chars' => mb_strlen($str, 'UTF-8'),
        'total_bytes' => strlen($str),
        'chinese' => $chinese,
        'english' => $english,
        'digits' => $digits,
        'spaces' => $spaces,
        'punctuation' => $punctuation,
    ];
}

print_r(analyzeString('你好 World 2024!'));
// ['total_chars' => 14, 'total_bytes' => 18, 'chinese' => 2, 'english' => 5, 'digits' => 4, 'spaces' => 2, 'punctuation' => 1]

注意事项

编码一致性

整个项目必须统一使用一种编码(推荐 UTF-8)。混合编码会导致乱码和难以调试的错误。确保 PHP 文件、数据库、HTTP 头部、HTML 页面都使用相同的编码。

mb_detect_encoding 不完全可靠

mb_detect_encoding() 使用启发式方法检测编码,对于短字符串或包含特定字符的字符串,检测结果可能不准确。在生产环境中,应明确知道编码来源,不要依赖自动检测。

mb_internal_encoding 全局影响

mb_internal_encoding() 设置的是全局默认编码,影响所有省略了编码参数的 mb_* 函数调用。在共享代码中修改全局编码可能导致其他代码出错。建议在调用时显式指定编码。

最佳实践

  1. 始终使用 UTF-8 编码:现代 PHP 项目应统一使用 UTF-8 编码
  2. 显式指定编码参数:在 mb_* 函数调用中显式传递 'UTF-8',不依赖全局设置
  3. 多字节字符串使用 mbstring:所有涉及中文、日文等非 ASCII 字符的操作都应使用 mb_* 函数
  4. 设置 php.ini 编码配置:在 PHP 配置中统一设置 mbstring.internal_encoding
  5. HTML 指定编码:使用 <meta charset="UTF-8"> 确保 HTML 页面编码正确
  6. 数据库连接指定编码:PDO 连接时指定 charset=utf8mb4

参考链接