编码与‘编码’

编码——是我们平时经常提到的一个词,在搞清楚这个词之前,我们先来看一段有趣的对话:

这篇博文写得行云流水,博主你方便的时候把二维码给我,大爷我今天要打赏!
好的先生,感谢您的拜读,我这就把二维码传给您。
恩,我先去方便一下,回来就给您打赏。大爷的,今天晚饭没喝粥,突然肚子疼。
哈哈,您身体快赶上老大爷了。

编码就像上述对话中的词语,懂的人自然知道,不同语义下其含义不同。人们所说的编码,有时指字符集,有时又用它指字符编码,有时却又指码位。下面我们就介绍下这些究竟是什么。

  • 字符集:一系列字符的集合。
  • 码位:将抽象的字符集中每一个字符映射到一个整数,此整数即为码位。
  • 字符编码:按照某种规则,将程序数据(字节序列)和字符集的码位进行互转的方法。

下面以我们常说的Unicode为例,Unicode是一个编码字符集,并不是一种字符编码,由于微软习惯把UTF-16LE(Little Endian)称做Unicode编码,所以误导了大部分人。UTF-8 UTF-16(LE/BE) UTF-32(LE/BE)才是针对Unicode字符集的编码方式。

简单的概述下编码的历史,期间具体过程不在本文再做阐述。最开始美国佬认为ASCII即可表示世界上所有字符,后来经过不断拓展,经历了ISO和Unicode从两败俱伤走向强强联手阶段,最终形成了现在的Unicode。目前的Unicode字符分为17组编排,0x0000 至 0x10FFFF,每组称为平面(Plane),而每平面拥有65536个码位,共1114112个。足够涵盖世界上任何国家的所有字符。

几种常见编码

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码) :标准形式为7bit,ASCII字符集有127个字符,ASCII编码采用0xxxxxxx格式的无符号整数。
  • Latin1 :同样为单字节编码,向下兼容ASCII,其码位范围是0x00-0xFF0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。其编码方式为8bit无符号整数即本身码位值。
  • ANSI : 为一个标准,不代表具体某种特定编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。
  • UTF-8( 8-bit Unicode Transformation Format) UTF-16(LE/BE) UTF-32(LE/BE)

几种UTF编码

下面讲解下几种UTF编码分别是如何工作的。

UTF-8

Unicode码位(十六进制) UTF-8 字节流(二进制)
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

按照上述模板,可以将Unicode所有码位,换算成UTF-8模式的字节序列(将码位的二进制形式依次填充模板中的x)。并且UTF-8完全向下兼容ASCIILatin-1

UTF-8的特点是对不同码位范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是6个字节。从上表可以看出,6字节模板有31个x,即可以容纳31位二进制数字。Unicode的最大码位0x7FFFFFFF也只有31位。

UTF-16

UTF-16是用2字节表示一个Unicode的码位,所以和ASCII并不兼容,因为就算0x00-0xFF范围内的编码,也需要用2字节表示。UTF-16在表示上有大小端之分,即字节序。

UTF-32

UTF-32 是一种非常费内存但是转换方法简单的编码,每个字符均为4字节,以32位无符号整数为单位 ,这种做法的好处是无需转换,正好对应了Unicode的码位。UnicodeUTF-32编码就是其对应的32位无符号整数。同样的UTF-32也有大小端之分。

关于字节序

通过上面的描述,我们知道字节序有Little Endian Big Endian 之分。在我们传输过程中,要根据字节序,进行相应的编解码,才可以的到准确的数据。

endian一词来源于乔纳森·斯威夫特的小说格列佛游记。小说中,小人国为水煮蛋该从大的一端剥开还是小的一端剥开而争论,争论的双方分别被称为Big Endian Little Endian。同样的战争也爆发在我们字节传送的过程中。1980年,Danny Cohen在其著名的论文On Holy Wars and a Plea for Peace中为平息一场关于字节该以什么样的顺序传送的争论而引用了该词。

BOM

Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字符“零宽无中断空格”。这个字符的编码是FEFF,而反过来的FFFE(UTF-16)FFFE0000(UTF-32)Unicode中都是未定义的码位,不应该出现在实际传输中。

下表是各种UTF编码的BOM

UTF编码 Byte Order Mark (BOM)
UTF-8 without BOM
UTF-8 with BOM EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

UTF-8本身并不需要用BOM来区分,因为他传输过程中没有字节序一说,仅仅是微软为其加上了BOM,用于识别UTF-8文件,其实并无作用。