Skip to content

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 文本有轻微性能影响。

最佳实践

  1. 中文匹配用 \p{Han}:比 Unicode 范围更完整
  2. 始终加 u 修饰符:使用 Unicode 属性时必须
  3. 多语言用 \p{L}\p{N}:替代 \w 支持所有语言
  4. 标点用 \p{P}:支持中英文标点
  5. 文本检测用 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} 更快。

参考链接