Unicode 正则匹配
概述
Unicode 正则表达式允许使用 Unicode 属性来匹配特定类别的字符,如中文、日文、韩文、数字、标点等。配合 PCRE 的 u 修饰符,PHP 能够精准处理多语言文本匹配。
基础概念
Unicode 属性语法
| 语法 | 含义 |
|---|---|
\p{L} | 任何语言的字母 |
\p{N} | 任何语言的数字 |
\p{M} | 标记符号(重音等) |
\p{P} | 标点符号 |
\p{S} | 符号(货币、数学等) |
\p{Z} | 分隔符 |
\p{Han} | 汉字 |
\p{Hiragana} | 平假名 |
\p{Katakana} | 片假名 |
\p{Hangul} | 韩文 |
\P{...} | 反向属性(不属于该类) |
u 修饰符必须
所有 Unicode 属性匹配都必须使用 u 修饰符。
语法与代码
常用 Unicode 字符属性
php
<?php
declare(strict_types=1);
// \p{L} - 任何语言的字母
preg_match_all('/\p{L}+/u', 'Hello世界Привет', $matches);
// ['Hello', '世界', 'Привет']
// \p{N} - 任何语言的数字
preg_match_all('/\p{N}+/u', '123四五六٧٨٩', $matches);
// ['123', '四五六', '٧٨٩']
// \p{P} - 标点符号
preg_match_all('/\p{P}/u', 'Hello, World! 你好吗?', $matches);
// [',', '!', ',', '?']
// \p{Han} - 匹配汉字
preg_match_all('/\p{Han}+/u', '你好世界Hello', $matches);
// ['你好世界']中文字符匹配
php
<?php
declare(strict_types=1);
// 匹配纯汉字字符串
preg_match('/^[\p{Han}]+$/u', '你好世界', $match); // true
preg_match('/^[\p{Han}]+$/u', 'Hello世界', $match); // false
// 中文名(2-4 个汉字)
preg_match('/^[\p{Han}]{2,4}$/u', '张三', $match); // true
preg_match('/^[\p{Han}]{2,4}$/u', '欧阳修', $match); // true
// 提取中文字符
$text = 'PHP是一种编程语言,中文是世界上最古老的语言之一。';
preg_match_all('/\p{Han}+/u', $text, $matches);
// ['是一种', '编程语言', '中文', '是世界上', '最', '古老', '的', '语言', '之一']
// 匹配中文+数字+标点
preg_match('/^[\p{Han}\p{N}\p{P}]+$/u', '你好,123!', $match); // true日文和韩文字符匹配
php
<?php
declare(strict_types=1);
// 日文
preg_match_all('/\p{Hiragana}+/u', 'こんにちは世界', $matches);
// ['こんにちは']
preg_match_all('/\p{Katakana}+/u', 'コンニチハ世界', $matches);
// ['コンニチハ']
// 韩文
preg_match_all('/\p{Hangul}+/u', '안녕하세요 Hello', $matches);
// ['안녕하세요']
// 混合 CJK 文本
$text = 'こんにちは、안녕하세요、你好';
preg_match_all('/[\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]+/u', $text, $matches);
// ['こんにちは', '안녕하세요', '你好']Unicode Script 属性检测
php
<?php
declare(strict_types=1);
// Unicode Script 属性
function detectScript(string $text): string
{
if (preg_match('/[\p{Han}]/u', $text)) return 'Chinese/CJK';
if (preg_match('/[\p{Hiragana}\p{Katakana}]/u', $text)) return 'Japanese';
if (preg_match('/[\p{Hangul}]/u', $text)) return 'Korean';
if (preg_match('/[\p{Arabic}]/u', $text)) return 'Arabic';
if (preg_match('/[\p{Cyrillic}]/u', $text)) return 'Cyrillic';
if (preg_match('/[\p{Latin}]/u', $text)) return 'Latin/Western';
return 'Unknown';
}
echo detectScript('你好世界'); // Chinese/CJK
echo detectScript('こんにちは'); // Japanese
echo detectScript('안녕하세요'); // Korean
echo detectScript('Привет мир'); // Cyrillic\w 与 \p{L} 的区别
php
<?php
declare(strict_types=1);
// \w = [a-zA-Z0-9_](只匹配 ASCII)
preg_match('/\w+/u', 'café', $match); // 'caf'(不匹配 é)
// \p{L} = 任何语言的字母(Unicode)
preg_match('/\p{L}+/u', 'café', $match); // 'café'
// 推荐的 Unicode 安全写法
// 使用 \p{L} 替代 [a-zA-Z],\p{N} 替代 [0-9]详细说明
Unicode 属性分类
通用类别(General Category)
| 缩写 | 含义 |
|---|---|
L | 字母 |
Lu | 大写字母 |
Ll | 小写字母 |
Lt | 首字母大写 |
N | 数字 |
Nd | 十进制数字 |
P | 标点 |
S | 符号 |
Z | 分隔符 |
优先使用 Unicode 属性
使用 \p{Han} 替代 Unicode 范围 \x{4e00}-\x{9fa5},前者更完整,覆盖所有汉字区段。
实战示例
多语言文本清洗
php
<?php
declare(strict_types=1);
function cleanMultilingualText(string $text): string
{
$pattern = '/[^\p{Han}\p{Latin}\p{N}\s\p{P}.,;:!?。,;:!?、]/u';
return preg_replace($pattern, '', $text);
}
echo cleanMultilingualText('Hello世界!This is a test。测试123');
// Hello世界!This is a test。测试123文本分词(中英文混合)
php
<?php
declare(strict_types=1);
function tokenizeMixedText(string $text): array
{
preg_match_all('/[a-zA-Z]+/u', $text, $en);
preg_match_all('/\p{Han}+/u', $text, $zh);
preg_match_all('/\d+/u', $text, $num);
return array_merge($en[0], $zh[0], $num[0]);
}
$text = 'PHP编程语言有100个内置函数';
print_r(tokenizeMixedText($text));
// ['PHP', '编程语言', '有', '100', '个', '内置函数']Emoji 与特殊字符处理
php
<?php
declare(strict_types=1);
// Emoji 长度统计(使用 mb_strlen)
$emoji = '😀😃😄😁😆😅🤣😂🙂';
echo mb_strlen($emoji, 'UTF-8'); // 10(每个 Emoji 是一个字符)
// 移除 Emoji
function stripEmoji(string $text): string
{
return preg_replace(
'/[\x{1F000}-\x{1FFFF}]|[\x{2600}-\x{27BF}]|[\x{FE00}-\x{FE0F}]|[\x{1F900}-\x{1F9FF}]/u',
'',
$text
);
}
echo stripEmoji('Hello 😀 World 🎉');
// Hello World
// 检测文本中的主要语言
function detectLanguages(string $text): array
{
$languages = [];
if (preg_match('/[\p{Han}]/u', $text)) $languages[] = 'Chinese';
if (preg_match('/[\p{Latin}]/u', $text)) $languages[] = 'Latin';
if (preg_match('/[\p{Cyrillic}]/u', $text)) $languages[] = 'Cyrillic';
if (preg_match('/[\p{Hiragana}\p{Katakana}]/u', $text)) $languages[] = 'Japanese';
if (preg_match('/[\p{Hangul}]/u', $text)) $languages[] = 'Korean';
if (preg_match('/[\p{Arabic}]/u', $text)) $languages[] = 'Arabic';
return $languages;
}
print_r(detectLanguages('Hello 世界!Привет 안녕하세요'));
// ['Chinese', 'Latin', 'Cyrillic', 'Korean']注意事项
\p{Han} 的范围
\p{Han} 包含中日韩统一表意文字的所有区段,包括扩展区。会匹配一些罕见汉字和日韩汉字。
u 修饰符性能
启用 u 修饰符后,正则引擎以 UTF-8 模式运行,对纯 ASCII 文本有轻微性能影响。
最佳实践
- 中文匹配用 \p{Han}:比 Unicode 范围更完整
- 始终加 u 修饰符:使用 Unicode 属性时必须
- 多语言用 \p{L}\p{N}:替代
\w支持所有语言 - 标点用 \p{P}:支持中英文标点
- 文本检测用 Script 属性:
\p{Latin}、\p{Han}等
Unicode 字符属性完整参考
php
<?php
declare(strict_types=1);
// 通用类别属性(General Category)
$gc = [
'L' => '字母(Letter)',
'Lu' => '大写字母(Uppercase Letter)',
'Ll' => '小写字母(Lowercase Letter)',
'Lt' => '首字母大写(Titlecase Letter)',
'Lm' => '修饰字母(Modifier Letter)',
'Lo' => '其他字母(Other Letter)',
'M' => '标记(Mark)',
'Mn' => '非间距标记(Nonspacing Mark)',
'Mc' => '间距组合标记(Spacing Combining Mark)',
'N' => '数字(Number)',
'Nd' => '十进制数字(Decimal Number)',
'Nl' => '字母数字(Letter Number)',
'No' => '其他数字(Other Number)',
'P' => '标点(Punctuation)',
'Pd' => '短划线(Dash Punctuation)',
'Ps' => '开始标点(Open Punctuation)',
'Pe' => '结束标点(Close Punctuation)',
'S' => '符号(Symbol)',
'Sm' => '数学符号(Math Symbol)',
'Sc' => '货币符号(Currency Symbol)',
'Sk' => '修饰符号(Modifier Symbol)',
'Z' => '分隔符(Separator)',
'Zs' => '空格分隔符(Space Separator)',
'Zl' => '行分隔符(Line Separator)',
'Zp' => '段落分隔符(Paragraph Separator)',
'C' => '其他(Other)',
'Cc' => '控制字符(Control)',
'Cf' => '格式字符(Format)',
];
// Script 属性
$scripts = [
'Latin' => '拉丁字母(英法德等)',
'Greek' => '希腊字母',
'Cyrillic' => '西里尔字母(俄语等)',
'Arabic' => '阿拉伯字母',
'Hangul' => '韩文字母',
'Han' => '汉字',
'Hiragana' => '日文平假名',
'Katakana' => '日文片假名',
'Thai' => '泰文字母',
'Devanagari' => '天城文(印地语等)',
'CJK' => '中日韩统一文字',
];常用 Unicode 正则模式
php
<?php
declare(strict_types=1);
// 匹配各种语言文本
$patterns = [
'中文' => '/^[\p{Han}\p{P}\s]+$/u',
'英文' => '/^[\p{Latin}\p{P}\s]+$/u',
'日文' => '/^[\p{Hiragana}\p{Katakana}\p{Han}]+$/u',
'韩文' => '/^[\p{Hangul}]+$/u',
'阿拉伯文' => '/^[\p{Arabic}]+$/u',
'俄文' => '/^[\p{Cyrillic}]+$/u',
'数字' => '/^[\p{Nd}]+$/u',
'标点' => '/^[\p{P}]+$/u',
'货币' => '/^[\p{Sc}]+$/u',
'空白' => '/^[\p{Zs}\t\r\n]+$/u',
'控制字符' => '/^[\p{Cc}]+$/u',
'字母+数字' => '/^[\p{L}\p{N}]+$/u',
'可打印字符' => '/^[\p{L}\p{N}\p{P}\p{S}\p{Zs}]+$/u',
];
// 统计字符串中各类型字符的数量
function charTypeStats(string $text): array
{
$stats = [
'letters' => preg_match_all('/\p{L}/u', $text),
'numbers' => preg_match_all('/\p{N}/u', $text),
'punctuation' => preg_match_all('/\p{P}/u', $text),
'spaces' => preg_match_all('/\p{Zs}/u', $text),
'symbols' => preg_match_all('/\p{S}/u', $text),
'chinese' => preg_match_all('/\p{Han}/u', $text),
'latin' => preg_match_all('/\p{Latin}/u', $text),
'other' => 0,
];
$total = mb_strlen($text, 'UTF-8');
$known = array_sum($stats);
$stats['other'] = $total - $known;
return $stats;
}
$text = 'Hello 世界!2024年 PHP 8.1 发布。';
print_r(charTypeStats($text));Unicode 正则属性速查表
| 属性 | 含义 | 匹配示例 |
|---|---|---|
\p{L} | 字母 | A, Z, a, z, 中文字 |
\p{Lu} | 大写字母 | A, B, C |
\p{Ll} | 小写字母 | a, b, c |
\p{N} | 数字 | 0-9, 一二三四 |
\p{Nd} | 十进制数字 | 0-9(各种语言) |
\p{P} | 标点 | . , ! ? ,。 |
\p{S} | 符号 | $ @ # ¥ € |
\p{Zs} | 空格 | 普通空格 |
\p{Han} | 汉字 | 你好世界 |
\p{Latin} | 拉丁字母 | A-Z, a-z |
\p{Cyrillic} | 西里尔字母 | А-я |
\p{Arabic} | 阿拉伯字母 | ا ب ت |
\p{Hangul} | 韩文 | 가 나 다 |
\p{Hiragana} | 平假名 | あ い う |
\p{Katakana} | 片假名 | ア イ ウ |
Unicode 属性的性能
Unicode 属性匹配比 ASCII 字符类匹配慢。在纯 ASCII 场景中,使用 \d、\w、[a-z] 比 \p{Nd}、\p{L}、\p{Latin} 更快。