学而不思则罔

本着”学而不思则罔”的态度, 在上篇Blog理解了ASCII、Unicode、UTF-8之间关系与含义之后, 我们将继续研究字符编码是如何在日常的程序处理中发挥作用的。
关于正则几乎所有的代码搬运工都会运用到, 无论在什么样的应用场景正则总能发挥它神奇的匹配能力, 那么正则是如何正确的匹配出中文的呢? 接下来就让我们一一实验。

实践才是检验真理的唯一标准

首先我们从google上去扒下两段匹配中文的正则代码。如下:

1
2
3
preg_match("/[\x7f-\xff]/", '我是中文')
preg_match("/[\x{4e00}-\x{9fa5}]/u", '我是中文')

然后我们开始去实验这两段代码匹配中文、英文、数字的情况。

1
2
3
4
5
6
7
8
preg_match("/[\x7f-\xff]/", '中文')
preg_match("/[\x{4e00}-\x{9fa5}]/u", '中文')

preg_match("/[\x7f-\xff]/", 'english')
preg_match("/[\x{4e00}-\x{9fa5}]/u", 'english')

preg_match("/[\x7f-\xff]/", 1)
preg_match("/[\x{4e00}-\x{9fa5}]/u", 1)

结果:

1
1 1 0 0 0 0 

我们可以看到, 匹配中文的前两句代码都返回了1代表匹配成功, 后面四句全部返回0代表失败。这样就验证了这两句代码确实能够正确的匹配到中文。

/[\x7f-\xff]/

首先我们来看第一句中关键的[\x7f-\xff], 这段被称作”模式”。 很显然”\x”代表这个一个十六进制的数字 ,”\x7f”翻译十进制后等于127, “\xff”等于255, 而”-“代表范围从什么值到什么值, “[]”代表其中的任何一个。 那这段的含义已经很明了了, 目标字符串凡是出现在127~255区间内都能够匹配得上。但是一想我们匹配的是中文啊, 它为什么能够在这段区间内呢?
通过上篇Blog我们其实就能够明白, 我们肉眼说能够看到的字符, 其实在计算机内存储的都只是数字, 只不过在展现的当下按照某种编码字典把数字替换成了对应的字符而已。查看IED现在用的是UTF-8编码, 然后我们用PHP输出”中”字对应UTF-8码。

1
2
3
4
$a = "中";
for ($i = 0; $i < strlen($a); $i++) {
echo ord($a[$i]) . ' ';
}

返回结果:

1
2
228 184 173

是不是突然发现了什么, “中”字是由三个数字(三字节)组成的, 而这三个数字都在127~255之间。 具体匹配的过程是把其中的每一个数字(字节)单独去和模式匹配, 如果有一个数字(字节)与模式匹配上,就代表目标字符串与模式匹配上了。228 184 173这三个数字(三字节)在UTF-8码下对应着”中”字, 再把这三个数字转换成十六进制数字0xe4b8ad。为了确认UTF-8码的”中”是否真的为0xe4b8ad, 我们来小小的验证一把。

  1. 打开汉字Unicode对应表, 找到”中”字的Unicode编码0x4e2d
  2. 转成二进制 1001110 00101101
  3. 按照Unicode转UTF-8(参考上篇Blog), 11100100 10111000 10101101.
  4. 最后转成十六进制0xe4b8ad

果然”中”字的UTF-8码为0xe4b8ad。而数字与英文的字符串之所以没有匹配上模式, 是因为在UTF-8码下, 数字与英文的字符串对应的数字都分布在0~127之间。具体可以参考Unicode向下兼容的ASCII码。

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

接下来我们来看第二句的\x{4e00}-\x{9fa5}, 与上句有些差别的是这里的十六进制数字变化, 并且最后加了小u的修正符。其实这里的”\x4e00”正是Unicode编码中第一位中文字符”一”的编码,而”\x9fa5”为Unicode编码中文最后一位”龥”, 具体可以参考Unicode文档。 而对于小u修正符我们打开PHP文档, 找到关于小u的修正符描述:

此修正符打开一个与 perl 不兼容的附加功能。 模式和目标字符串都被认为是 utf-8 的。 无效的目标字符串会导致 preg_* 函数什么都匹配不到; 无效的模式字符串会导致 E_WARNING 级别的错误。 PHP 5.3.4 后,5字节和6字节的 UTF-8 字符序列被考虑为无效(resp. PCRE 7.3 2007-08-28)。 以前就被认为是无效的 UTF-8。

可以得知当我们用了小u修正符后, 模式和目标字符串都会被认为是UTF-8码。其中模式的十六进制都会被转成UTF-8码, 而目标字符串会变被当成一个整体的UTF-8码(三个字节)。最后拿”中”字的UTF-8码0xe4b8ad看是否出现在0xe4b880 ~ 0xe9bea5f范围内。

从最初开始

一个很难的知识点或是一个新的工具总是由许许多多的小知识点、小工具所堆积所重新组合而成的, 而我们所要做的是从点点滴滴学起, 从最初开始认识解刨这个计算机世界。

参考

PHP字符串中用正则表达式匹配中文出现乱码 segmentfault
Unicode中文编码表
Unicode编码表大全
Unicode转UTF-8