PHP 正则 Unicode 模式处理多语言文本匹配

发布于
2

在 PHP 中使用 preg_* 系列函数处理中文、日文、emoji 等多语言文本时,默认的 PCRE 引擎按单字节解析,会直接导致多字节 UTF-8 字符被拆碎、匹配失败或乱匹配。开启 Unicode 模式(/u 修饰符)后,PCRE 按 UTF-8 编码单元处理,并结合 \p{} / \P{} 语法才能正确命中多语言内容。

为什么要开 /u

PHP 的 preg_* 底层是 PCRE 库,未加 /u 时:

  • . 只匹配单字节,一个中文汉字占 3 字节,会被拆成 3 次匹配

  • \w\d\s 只认 ASCII 范围,中文、全角数字、全角空格全部不匹配

  • 字符类 [一-龥] 这种”汉字区间”写法依赖具体编码顺序,UTF-8 下不可靠且覆盖不全

// 不加 /u:匹配不到
preg_match('/中文/', '中文测试', $m);   // ✅ 纯字面量碰巧能中(字节一致)
preg_match('/^.$/', '中', $m);          // ❌ . 只吃到 1 字节
preg_match('/^\w+$/', '中文test', $m);  // ❌ 中文部分不认
// 加 /u 后
preg_match('/^.$/u', '中', $m);          // ✅ $m[0] = "中"
preg_match('/^\w+$/u', '中文test', $m);  // ❌ \w 仍只认 ASCII,还是失败

注意:/u 只解决”按 UTF-8 逐字符走”,不解决”\w 是否包含汉字”——后者要靠 \p{}

/u 修饰符的基本用法

u 加在定界符后面即可,和其他修饰符(is)叠加无冲突:

$text = 'PHP 是世界上最好的语言?';
if (preg_match('/^PHP(.+)$/u', $text, $m)) {
    echo $m[1]; // " 是世界上最好的语言?"
}

前置条件:传入的 $text 本身必须是合法 UTF-8,否则 PCRE 会报 PCRE_ERROR_BADUTF8preg_last_error() 返回 PREG_BAD_UTF8_ERROR。生产代码里建议先用 mb_check_encoding($text, 'UTF-8') 兜底。

Unicode 属性 \p{} 才是多语言主力

/u 开了之后,PCRE 支持 Unicode Character Properties,语法:

  • \p{Category} —— 匹配某类字符

  • \P{Category} —— 反向(大写 P)

  • \X —— 匹配一个”扩展字形簇”,能完整吃掉 emoji 组合(👨‍👩‍👧‍👦 这种 ZWJ 拼接的)

常用类别对照:

写法

含义

\p{L}

任意语言的字母(含中文、日文、阿拉伯文等)

\p{N}

任意文字的数字(含全角 123)

\p{Han}

汉字(CJK Unified Ideographs)

\p{Hiragana}

平假名

\p{Katakana}

片假名

\p{Sc}

货币符号(¥ $ €)

\p{Emoji}

Emoji(PCRE 10+ / PHP 7.4+ 才稳)

\p{Z}

所有空白分隔符(含全角空格、NBSP)

实战:提取字符串里的汉字

$text = 'Tom 说:「こんにちは、世界!」然后笑了😄';
preg_match_all('/\p{Han}+/u', $text, $m);
print_r($m[0]);
// ["说", "然后笑了"]

比老的 /[\x{4e00}-\x{9fa5}]/u 区间写法覆盖更全(生僻字、扩展 B~G 区都吃得到)。

实战:混语文本里只留”字母 + 数字 + 空格”

$text = 'ABC 123 中文 🎌';
$clean = preg_replace('/[^\p{L}\p{N}\p{Z}]+/u', '', $text);
echo $clean; // "ABC 123 中文 "(emoji 和 🎌 被干掉,全角数字因 \p{N} 保留)

这里 \p{Z} 能吞掉全角空格(U+3000),比 \s/u 下更宽(\s 在 Unicode 模式也只扩了一点点,不全)。

几个容易踩的坑

1. emoji 不是单个 \p{L}

普通 emoji ✅ 是 1 码点,但 👨‍👩‍👧‍👦、🏳️‍🌈 这种是多个码点用 ZWJ(U+200D)拼的。\p{Emoji} 只匹配其中一段,想整段吞用 \X

$text = '一家👨‍👩‍👧‍👦去公园';
preg_match_all('/\X/u', $text, $m);
// 每个元素是一个"视觉字符",👨‍👩‍👧‍👦 不会被拆

2. 中文标点不在 \w

即便开了 /u\w = [A-Za-z0-9_],汉字也不在内。想”单词”含汉字,得自己拼:

// 近似"多语言单词":字母 / 汉字 / 数字 / 下划线
preg_match_all('/[\p{L}\p{Han}\p{N}_]+/u', $text, $m);

3. us 一起用时,. 仍不吃 \n 以外的垂直空白?

s.\n,但 Unicode 里还有 \r\v 等——不过这个和 /u 无关,是 PCRE 的老行为,记一下就行。

4. 性能

\p{Han} 这类属性匹配比区间 [\x{4e00}-\x{9fa5}] 略慢一点(PCRE 要查 Unicode 表),但差的是纳秒级,换来的是正确性,没必要省。

什么时候不用 /u

  • 确定文本就是纯 ASCII(比如邮箱校验、URL 校验),开了也没坏处但无意义

  • 超大文本 + 高频循环场景,可以用 mb_ereg_* 或先 mb_split 拆块再 preg_,避免单次正则把 UTF-8 逐字节扫太重

常见组合速查

// 整串是否纯汉字
preg_match('/^\p{Han}+$/u', $s);

// 是否含中日韩(CJK 统一表意 + 扩展 A~G,\p{Han} 已覆盖)
preg_match('/\p{Han}/u', $s);

// 去掉所有 emoji(PCRE 支持 \p{Emoji} 的版本)
$noEmoji = preg_replace('/\p{Emoji}/u', '', $s);

// 拆成"视觉字符"数组(等效 mb_str_split 但能正确处理 ZWJ emoji)
preg_match_all('/\X/u', $s, $m);
$chars = $m[0];

PHP 正则处理多语言文本的核心就两条:/u 保证按 UTF-8 走,\p{} 保证按 Unicode 属性认字。剩下的是按业务挑类别(Han / Hiragana / N / Z)拼pattern,比硬写码点区间稳得多。

常见问题(FAQ)

如何在 PHP 中正确处理中文、日文、emoji 等多语言文本的正则匹配?
在 PHP 中处理多语言文本匹配时,需要开启 PCRE 的 Unicode 模式(添加修饰符 /u)。这能让 PCRE 按 UTF-8 编码单元解析文本,避免多字节字符被拆碎。此外,还需使用 Unicode 属性语法(如 p{}),例如 p{Han} 匹配汉字、p{Emoji} 匹配表情符号,以正确识别多语言字符。同时,确保输入文本为合法 UTF-8 编码。
0 讨论
热门最新
总结
暂无总结
0 / 600
嗨,下午好!
所有的成功,都源自一个勇敢的开始
¥10.00
10元抵扣券
已过期