常用字符编码总结

相关机构与历史

(1) 国际标准化组织(ISO),他们于1984年创建ISO/IEC JTC1/SC2/WG2工作组,试图制定一份“通用字符集”(Universal Character Set,简称UCS),并最终制定了ISO 10646标准。
(2) 统一码联盟,他们由Xerox、Apple等软件制造商于1988年组成,并且开发了Unicode标准(The Unicode Standard)。

1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都独立存在,并独立地公布各自的标准。不过由于Unicode这一名字比较好记,因而它使用更为广泛。
Unicode编码点分为17个平面(plane),每个平面包含216(即65536)个码位(code point)。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0016到1016,共计17个平面。

什么是字符集和字符编码

字符集(Charset):符号的集合,每个符合有个唯一数字表示(称之为码位,code point)。但是这个数字在计算机上如何存储,字符集不关心。下面是unicode字符集中的一个字符和其对应的唯一码位。
image.png
字符编码(Character Encoding):字符编码和字符集的重要区别是,字符编码约定了字符集的码位在计算机上是如何存储的。计算机是用二进制来表示和传输数据的。人理解的字符要计算机处理,也必须处理成二进制的格式。将字符集的码位和二进制数映射起来的过程可以理解为编码。一个7位的二进制数可以表示2^7=128个码位,也就是意味着最多可以将128个字符的码位表示成二进制数。

注意:一般来说字符集和字符编码名字都是相同的,比如ASCII字符集关联的事ASCII编码,GBK字符集关联GBK字符编码。

常见字符集字符编码

ASCII字符集&字符编码

这个是60年代美国人搞出来给自己用的。码位用127个数字表示。码位的存储上,最高位一直是0,后7位表示128个字符,这个对于英文来说是很足够了。ASCII字符编码中的每个字符占用空间大小为1字节。欧洲的很多其他国家主要也使用英文字母,特定的字符集都是拓展了ASCII,将其最高位利用起来,将编码值范围 (128,255] 利用起来表示本国特有的一些字符。

Unicode字符集

这个字符集非常大,码位的范围是[0,0x10FFFF],最多可以容纳17 × 65,536 = 1,114,112个字符。unicode只是约定了字符集,但是没有约定这些码位如何存储。不同unicode字符编码方式的差异主要体现在存储开销和索引性能上的差异。

UTF-8

查看unicode的码位,可以看到不同的码位存储需要的最小字节数都是不同的。例如000F使用1个字节就可以存储,但是FFFF就需要至少2个字节才可以存储的下。** UTF-8是一个变长字符编码**,它将unicode的码位按照存储使用的字节数进行划分,不同的码位在存储上采用不同的编码方式,对于空间会更加节约一些,存储占用的字节数为1~4字节。
UTF-8 的编码规则:

  1. 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的, 所以 UTF-8 能兼容 ASCII 编码,这也是互联网普遍采用 UTF-8 的原因之一
  2. 对于 n 字节的符号( n > 1),第一个字节的前 n 位都设为 1,第 n + 1 位设为 0,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码 (高位空余则补0)
Unicode编码范围(16进制) UTF-8编码模板(二进制)
000000 - 00007F 0xxxxxxx (和ASCII码保持一致)
000080 - 0007FF 110xxxxx 10xxxxxx
000800 - 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
01 0000 - 10 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

下面通过一个例子来加深理解UTF-8编码。字符“万”的编码是4e07,在上面表格中属于第三档的范围,也就是说需要用1110xxxx 10xxxxxx 10xxxxxx来表示,4e07表示成二进制位是 0100 1110 0000 0111 ,将这个二进制位替换表格中第三档中编码模板中的x值,则得到**1110 **0100 1011 1000 1000 0111 (十六进制表示为 0xe4B887)

优点

  • 存储上节约开销

缺点

  • 无法表示完整的unicode字符,大于10 FFFF的字符无法表示
  • 不可以按照码位直接编码,涉及套用规则,有一些性能开销

UTF-16

UTF-16也是一种变长字符编码,字符存储占用为2字节或者4字节。
UTF-16编码规则如下:

  1. 对于 Unicode 码小于 0x10000 的字符, 使用 2 个字节存储,并且是直接存储 Unicode 码,不用进行编码转换
  2. 对于 Unicode 码在 0x10000 和 0x10FFFF 之间的字符,使用 4 个字节存储,这 4 个字节分成前后两部分,每个部分各两个字节,其中,前面两个字节的前 6 位二进制固定为 110110,后面两个字节的前 6 位二进制固定为 110111, 前后部分各剩余 10 位二进制表示符号的 Unicode 码 减去 0x10000 的结果
  3. 大于 0x10FFFF 的 Unicode 码无法用 UTF-16 编码
Unicode编码范围(16进制) UTF-16编码方式(二进制)
0000 0000 - 0000 FFFF xxxxxxxx xxxxxxxx (2字节)
0001 0000 - 0010 FFFF 110110yy yyyyyyyy 110111xx xxxxxxxx (4字节)

套用编码规则的方式与utf-8相同,这里不赘述了。

优点

  • 存储开销上相比UTF-8稍微大一些,但是仍然是尽量节约存储开销了

缺点

  • 无法表示完整的unicode字符,大于10 FFFF的字符无法表示
  • 不可以按照码位直接编码,涉及套用规则,有一些性能开销

UTF-32

定长的编码方式。始终占用4个字节,可以表示完整的unicode字符。例如按照UTF32BE编码,万这个字符utf-32表示为0x00004e07

UCS-2

在Unicode与ISO 10646合并之前,ISO 10646标准为“通用字符集”(UCS)定义了一种16位的编码形式(即UCS-2),其编码固定占用2个字节(定长2字节编码),它包含65536个编码空间(可以为全世界最常用的63K字符编码,为了兼容Unicode,0xD800-0xDFFF之间的码位未使用)

UCS-4

在Unicode与ISO 10646合并之前,ISO 10646标准为“通用字符集”(UCS)定义了一种31位的编码形式(即UCS-4),其编码固定占用4个字节(定长4字节编码),编码空间为0x00000000~0x7FFFFFFF(可以编码20多亿个字符)。
UCS-4有20多亿个编码空间,但实际使用范围并不超过0x10FFFF,并且为了兼容Unicode标准,ISO也承诺将不会为超出0x10FFFF的UCS-4编码赋值。由此UTF-32编码被提出来了,它的编码值与UCS-4相同,只不过其编码空间被限定在了0~0x10FFFF之间。因此也可以说:UTF-32是UCS-4的一个子集。

多字节编码的字节序

多字节编码存储才会有字节序的问题,UTF-8 最小编码单元是一字节,所以 它是没有字节序的问题。计算机在解析UTF1-6和UTF-32这种多字节存储编码时需要关心其字节序。字节序主要是指编码单元是按照大端还是小端存储的。

  • 小端字节序简写为 LE( Little-Endian ):表示 低位字节在前,高位字节在后, 高位字节保存在内存的高地址端,而低位字节保存在内存的低地址端
  • 大端字节序简写为 BE( Big-Endian ):表示 高位字节在前,低位字节在后,高位字节保存在内存的低地址端,低位字节保存在在内存的高地址端

下图和表格展示了大小端存储数据的差异,大端展示比较符合人的阅读习惯;计算机读取内存地址是从低地址开始读取的,小端字节序更加符合计算机的处理顺序
image.png

物理地址 小端模式 大端模式
0x80008000 0x44 0x11
0x80008001 0x33 0x22

BOM

BOM 是 byte-order mark 的缩写,是 “字节序标记” 的意思, 它常被用来当做标识文件是以 UTF-8、UTF-16 或 UTF-32 编码的标记。在 Unicode 编码中有一个叫做 “零宽度非换行空格” 的字符 ( ZERO WIDTH NO-BREAK SPACE ), 用字符 FEFF 来表示。对于 UTF-16 ,如果接收到以 FEFF 开头的字节流, 就表明是大端字节序,如果接收到 FFFE, 就表明字节流 是小端字节序.

下面的表格列出了不同 UTF 格式的固定文件头.

UTF编码 固定文件头
UTF-8 EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

注意:这个BOM只是个文件头里面的标记,和编码规则没有关系,切勿弄混

数据库字符编码

MySQL

MySQL中也定义了UTF8的字符编码,但是不是真正意义上的UTF8,能表达的变长字符最多3个字节,所以很多占用4个字节的字符在utf8编码的表中是无法存储的。所以后来MySQL引入了utf8mb4字符集,这就相当于完整能力的UTF-8编码了。

参考资料