PHP正则表达式匹配所有中文字符并计算出现次数

发布于 2023-05-24  452 次阅读


内容纲要

在字符串中匹配所有中文字符,使用PHP的preg_metch_all()函数:

preg_match_all($pattern,$subject,[$matchs,$flags,$offsets]);
  • $pattern 搜索的模式,字符串类型,指定本次搜索使用的正则表达式
  • $subject 字符串类型 指定被匹配的字符串
  • $matchs 可选,指定本次匹配结果存放的变量,他的类型是一个多维数组,数组排序依据flags决定
  • $flags 可选,指定排序方法参数
  • $offsets 指定从哪一个位置开始查找,默认是从字符串开头进行查找。

$flags参数的取值:

PREG_PATTERN_ORDER

结果排序为$matches[0]保存完整模式的所有匹配, $matches[1] 保存第一个子组的所有匹配,以此类推。

PREG_SET_ORDER

结果排序为$matches[0]包含第一次匹配得到的所有匹配(包含子组), $matches[1]是包含第二次匹配到的所有匹配(包含子组)的数组,以此类推。

PREG_SET_CAPTURE

如果这个标记被传递,每个发现的匹配返回时会增加它相对目标字符串的字节偏移量。 注意这会改变matches中的每一个匹配结果字符串元素,使其 成为一个第0个元素为匹配结果字符串,第1个元素为 匹配结果字符串在subject中的偏移量。

PREG_UNMATCHED_AS_NULL

传入此标记,未匹配的子组报告为 null;否则会是空 string。

接下来考虑如何通过正则表达式匹配出中文,首先正则表达式的元字符中并没有表示中文的标记,所以考虑如何通过其他的方式将中文字符表达出来。

应该转义符和编码来匹配到中文,因为每个中文都有对应的unicode编码,而这些中文恰好是按顺序排列的一个组合,在Unicode编码中,有连续的一串编码表示的都是中文,所以只需要找到这一串编码即可。

通过Unicode字符对照得出,常见的中文汉字范围为:

4e00-9fa5

所以通过转义,再限定范围:

/[\u4e00-\u9fa5]/

这样匹配范围就是常见汉字的范围,测试一下:

$content=$data['content'];
$content="!!!。?!你好啊";
$a=preg_match_all("/[\u4e00-\u9fa5]/",$content,$rs);

抛出错误:

preg_match_all(): Compilation failed: PCRE does not support \L, \l, \N{name}, \U, or \u at offset 2

原来,PHP正则表达式中不支持下列 Perl 转义序列:\L, \l, \N, \P, \p, \U, \u, or \X

在PHP手册中给出:在”\x”后面,读取两个十六进制数(字母可以是大写或小写)。 在UTF-8模式, “\x{…}”允许使用, 花括号内的内容是十六进制有效数字。 它将给出的十六进制数字解释为 UTF-8 字符代码。原来的十六进制转义序列, \xhh, 匹配一个双字节的UTF-8字符,如果它的值大于127。

也就是说,在utf-8模式下,\x{...}可以将给定的\x后转义的十六进制数字解释为UTF-8字符编码,因此:

header('Content-Type=text/html;charset=utf-8')

将头部设置为utf-8编码,以防通过其他编码解析脚本,然后使用\x转义表示文字范围:

[\x{4e00}-\x{9fa5}]

测试一下:


$content="!!!。?!你好啊";
$a=preg_match_all("/[\x{4e00}-\x{9fa5}]/",$content,$rs);
var_dump($rs);

仍然报错:

preg_match_all(): Compilation failed: character value in \x{} or \o{} is too large at offset 8

继续翻手册,发现对于使用\x{...}进行字符编码匹配,需要加一个模式修饰符u来使模式和目标字符串都被认为是utf-8的,也就是使用utf-8模式进行正则匹配,所以:

$content="!!!。?!你好啊";
$a=preg_match_all("/[\x{4e00}-\x{9fa5}]/u",$content,$rs);
var_dump($rs);

运行结果:

array(1) { [0]=> array(3) { [0]=> string(3) "你" [1]=> string(3) "好" [2]=> string(3) "啊" } }

但是这时候只能匹配到常见的汉字,如果有标点符号,还有加上:

[\x{FF10}-\x{FF19}]|[\x{3000}-\x{303F}]|[\x{fe10}-\x{fe1f}]|[\x{ff00}-\x{ffef}]

测试一下:

$content="今天!我起了个大早。为什么?因为:我饿了!sdjaflkfdjksla.....!!";
$a=preg_match_all("/[\x{4e00}-\x{9fa5}]|[\x{FF10}-\x{FF19}]|[\x{3000}-\x{303F}]|[\x{fe10}-\x{fe1f}]|[\x{ff00}-\x{ffef}]/u",$content,$rs);
var_dump($rs);

结果:

array(1) { [0]=> array(23) { [0]=> string(3) "今" [1]=> string(3) "天" [2]=> string(3) "!" [3]=> string(3) "我" [4]=> string(3) "起" [5]=> string(3) "了" [6]=> string(3) "个" [7]=> string(3) "大" [8]=> string(3) "早" [9]=> string(3) "。" [10]=> string(3) "为" [11]=> string(3) "什" [12]=> string(3) "么" [13]=> string(3) "?" [14]=> string(3) "因" [15]=> string(3) "为" [16]=> string(3) ":" [17]=> string(3) "我" [18]=> string(3) "饿" [19]=> string(3) "了" [20]=> string(3) "!" [21]=> string(3) "!" [22]=> string(3) "!" } }

而preg_match_all返回的值就是匹配到的次数,这样就能完美的匹配一串字符串中中文字符出现的次数了。

届ける言葉を今は育ててる
最后更新于 2023-05-24