初识字符编码

563 阅读5分钟

计算机所能理解的数据是二进制数据,由一位一位的0或1来表示。每个数字被称为一个位(bit),而8位合称为一个字节(byte)。1kb=1024个byte。

ASCII码

数字要转换为二进制,只需要按照相应的进制转换即可。

而字符要转换为二进制数据,那么就要有一套映射规则来把每个字符都对应到一个相应的二进制数据串。

一开始计算机中主要使用的是英文字符,因此美国制定了一套字符编码:ASCII码。

它一共规定了128个字符的编码,例如大写字母A对应二进制串01000001。这128个字符都有各自唯一的二进制串。

由于2^7=128,所以对于二进制来说只需要7位就可以表示全部的128个字符。那么ASCII码规定剩下的第八位统一都为0。

Unicode

但随着互联网的发展,计算机中使用的字符不再仅限于英文字符,例如中文、法文等字符的引入,使得仅有8位的ASCII码无法表示所有的语言字符了。

这时人们就设计出了另外一种映射规则:Unicode码。它最多可以用16位来表示字符,因此它可以为地球上每种语言的每个字符都规定一个唯一的二进制编码。

Unicode只是规定了每个字符对应哪个二进制数据,但没有规定如何来存储这些二进制串。

以中文“汉”字符为例,它在Unicode编码中对应的二进制串是110110001001001,一共有15位。也就是说它需要两个字节来存储。

这就导致了一个问题:计算机怎么知道你这两个字节是表示一个字符,还是分别表示两个字符呢?

有人会说我们只要用最大的4字节来表示所有字符,不够的位补0,那么计算机在解码时就可以统一按每个字符4字节来处理了。

但这样会导致存储空间变大,例如一个英文字符A,它用4字节的二进制来存储的话占用的空间足足多了三倍。

UTF编码

为了解决上面的Unicode编码存储问题,出现了UTF-8、UTF-16和UTF-32这三种编码方式。

这三种编码方式各自有一套规则来规定如何存储Unicode字符对应的二进制数据。

UTF-32

其中UTF-32就是上述说的用4个字节来存储每个字符。但由于其空间浪费严重,所以不太流行。

UTF-8

其中最流行还是UTF-8编码。它的规则的最大特点是可变长。根据字符的不同,分别用1-4个字节来表示不同的字符,尽量节约了存储空间。

其编码规则如下:

  1. 对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。
  2. 对于n字节的字符(n>1),第一个字节的前n位都设为1,第n+1为设置为0。剩余的每个字节的前两位都设为10,剩下的二进制用该字符对应的Unicode码来由后往前填充。

image.png 以中文“汉”字符为例,它的Unicode码是0x00006c49(十六进制)。从上表得它位于第三行的十六进制范围内,也就是说需要用三个字节来表示。

那么它的UTF-8格式就是1110xxxx 10xxxxxx 10xxxxxx。接着将“汉”字符的二进制串 110 1100 0100 1001 由后往前填充到格式的x中,多余的x用0补上。

得到UTF-8的编码为 11100110 10110001 10001001,转换为十六进制就是0xE6 0xB1 0x89。

至此,可以把上面的UTF-8二进制串存入硬盘。下次读取时用UTF-8编码就知道这是个三字节字符。

UTF-16

因为Unicode中常用的字符都处于0 - 11111111(0x0 - 0xFFFF)的范围内,因此两个字节可以覆盖大部分的字符。两个字符的范围称之为基本平面。剩下的字符都放在辅助平面中。

UTF-16的编码规则是基本平面的字符占用两个字节,而辅助平面的字符占用4个字节。

那么遇到四个字节时,计算机是要解释为两个基本平面字节,还是一个辅助平面字符呢?

UTF-16在基本平面内设置0xD800 - 0XDFFF是一个空段,不对应任何字符,专门用来解决上面的问题。

因此,当我们遇到两个字节,发现它的码点在 U+D800 到 U+DBFF 之间,就可以断定,紧跟在后面的两个字节的码点,应该在 U+DC00 到 U+DFFF 之间,这四个字节必须放在一起解读。

JavaScript编码

JavaScript语言也是采用了Unicode字符集,但只支持一种编码UCS-2。这种编码会认为所有字符都是2个字节来表示。

那么在处理4字节的字符时就会非常麻烦。

因此ES6加强了对Unicode的支持,可以使用\uxxxx来用十六进制表示一个字符。但这样只限于\u0000 - \uFFFF之间的两字节字符。超出这个范围的可以有两种方法来表示:

  1. \uD842\uDFB7
  2. \u{20BB7}