UTF8、Unicode 字符编码原理
Unicode
计算机生于美帝,彼时字符也只有一个ASCII字符集:美国标码。使用7bit来表示128个字符:包含英文字母的大小写、 数字、各种标点符号和设备控制符。对于早期的程序来说,这就足够了,但是这也导致了世界上很多其他地区的用户无法直接使用自己的符号系统。随着互联网的发展,混合多种语言的数据变得很常见。
如何有效处理这些包含了各种语言的丰富多样的文本数据呢?— Unicode 出现了,它收集了这个世界上所有的符号系统,每个符号都分配一个唯一的Unicode 码点。
每个Unicode 码点都使用同样的大小4个字节来表示。这种方式比较简单统一,但是它会浪费很多存储空间。大部分文本是ASCII字符(ASCII字符只需要1字节就能表示),常用的字符也远少于65536个(2个字节表示常用的也可以了)。
UTF-8
说到Unicode的浪费空间的缺陷,更优化的方案么? UTF-8
UTF8是一个将Unicode码点编码为字节序列的变长编码。 理解这句话非常重要。 UTF8编码使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。
问题来了,这种变长的编码,如果多个字符在一起怎么区分出哪些个字节属于哪个字符编码呢?这就是UTF-8设计的重点。
每个符号编码后第一个字节的高bit位用于表示总共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应 7bit的ASCII字符,这能保证它和传统的 ASCII编码兼容。如果第一个字节的高 bit位是110,则说明需要2个字节。后续的每个高bit位都以10开头。更大的Unicode码点也是采用类似的策略处理。
对照表如下:字节序 高->低
第一个字节 | 二 | 三 | 四 | 最大有效位 | 表示的Unicode码点(十进制) |
---|---|---|---|---|---|
0xxxxxxx | 7个bit | 0-127 ASCII | |||
110xxxxx | 10xxxxxx | 11个bit | 128-2047 | ||
1110xxxx | 10xxxxxx | 10xxxxxx | 16个bit | 2048-65535 | |
11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 21个bit | 65536-2097151【目前Unicode 最大值为0x10FFFF,尚未编号到0x1FFFFF】 |
前文说过,UTF8就是Unicode的变长编码,除了规则中每个字节高位的表示,其他有效位就是Unicode的字节序列。
用汉字举个栗子
# PHP的json_encode,必须要求UTF8编码
<?php
echo json_encode('我');// "\u6211" 是个Unicode编码
\uhhhh 是Unicode码点值的16bit表示法。注意是Unicode。怎样转成UTF-8的表示法?
汉字『我』,Unicode码点值: 十六进制: 0x6211;二进制:110 001000 010001 二进制有效位15个,根据对照表,需要UTF8三个字节表示。(分段可以按照6,6,4 从低到高) 『我』用UTF-8编码的二进制即是: 11100110 10001000 10010001 如果对每个字节16进制表示则是: \xe6\x88\x91;转回Unicode:\u6211
中文汉字
汉字在Unicode的码点值范围是从 \u4E00 ~ \u9FA5,也就是两个字节。有效位15~16个bit,根据对照表,所有汉字字符UTF-8 都是三个字节表示。
PHP 匹配一个UTF-8编码的中文:
$str = '中';
preg_match("/[\x{4e00}-\x{9fa5}]/u", $str);
# 匹配参数/u参数是必不可少的,表示按照Unicode匹配
显然,和Unicode不同的编码实现,如果汉字是GBK的编码,不能用这个匹配表达式。