### [PHP 正则 Unicode 模式处理多语言文本匹配](https://www.huociguo.com/article/187) **Published:** 2026-07-18T15:04:41 **Author:** 米了 **Excerpt:** 在 PHP 中使用 preg_* 系列函数处理中文、日文、emoji 等多语言文本时,默认的 PCRE 引擎按单字节解析,会直接导致多字节 UTF-8 字符被拆碎、匹配失败或乱匹配。开启 Unicode 模式(/u 修饰符)后,PCRE 按 在 PHP 中使用 `preg_*` 系列函数处理中文、日文、emoji 等多语言文本时,默认的 PCRE 引擎按单字节解析,会直接导致多字节 UTF-8 字符被拆碎、匹配失败或乱匹配。开启 Unicode 模式(`/u` 修饰符)后,PCRE 按 UTF-8 编码单元处理,并结合 `\p{}` / `\P{}` 语法才能正确命中多语言内容。 ![](https://api.huociguo.com/wp-content/uploads/2026/07/96ecd5ec55209-18144c064b-1.png "96ecd5ec55209-18144c064b-1") ## 为什么要开 `/u` PHP 的 `preg_*` 底层是 PCRE 库,未加 `/u` 时: - `.` 只匹配单字节,一个中文汉字占 3 字节,会被拆成 3 次匹配 - `\w`、`\d`、`\s` 只认 ASCII 范围,中文、全角数字、全角空格全部不匹配 - 字符类 `[一-龥]` 这种”汉字区间”写法依赖具体编码顺序,UTF-8 下不可靠且覆盖不全 ```php // 不加 /u:匹配不到 preg_match('/中文/', '中文测试', $m); // ✅ 纯字面量碰巧能中(字节一致) preg_match('/^.$/', '中', $m); // ❌ . 只吃到 1 字节 preg_match('/^\w+$/', '中文test', $m); // ❌ 中文部分不认 ``` ```php // 加 /u 后 preg_match('/^.$/u', '中', $m); // ✅ $m[0] = "中" preg_match('/^\w+$/u', '中文test', $m); // ❌ \w 仍只认 ASCII,还是失败 ``` > 注意:`/u` 只解决”按 UTF-8 逐字符走”,不解决”\\w 是否包含汉字”——后者要靠 `\p{}`。 ## `/u` 修饰符的基本用法 把 `u` 加在定界符后面即可,和其他修饰符(`i`、`s`)叠加无冲突: ```php $text = 'PHP 是世界上最好的语言?'; if (preg_match('/^PHP(.+)$/u', $text, $m)) { echo $m[1]; // " 是世界上最好的语言?" } ``` 前置条件:**传入的** `$text` **本身必须是合法 UTF-8**,否则 PCRE 会报 `PCRE_ERROR_BADUTF8`,`preg_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) | ### 实战:提取字符串里的汉字 ```php $text = 'Tom 说:「こんにちは、世界!」然后笑了😄'; preg_match_all('/\p{Han}+/u', $text, $m); print_r($m[0]); // ["说", "然后笑了"] ``` 比老的 `/[\x{4e00}-\x{9fa5}]/u` 区间写法覆盖更全(生僻字、扩展 B~G 区都吃得到)。 ### 实战:混语文本里只留”字母 + 数字 + 空格” ```php $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`: ```php $text = '一家👨‍👩‍👧‍👦去公园'; preg_match_all('/\X/u', $text, $m); // 每个元素是一个"视觉字符",👨‍👩‍👧‍👦 不会被拆 ``` **2\. 中文标点不在** `\w` 即便开了 `/u`,`\w` = `[A-Za-z0-9_]`,汉字也不在内。想”单词”含汉字,得自己拼: ```php // 近似"多语言单词":字母 / 汉字 / 数字 / 下划线 preg_match_all('/[\p{L}\p{Han}\p{N}_]+/u', $text, $m); ``` **3\.** `u` **和** `s` **一起用时,**`.` **仍不吃** `\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 逐字节扫太重 ## 常见组合速查 ```php // 整串是否纯汉字 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,比硬写码点区间稳得多。 **Tags:** PHP, Unicode, 多语言 **Categories:** PHP教程 ---