2

网站开发中经常会被乱码问题困扰。知道文件编码错误会导致乱码,但对其中的原理却知之甚少。偶然从某篇文章了解了Unicode,发现从这条线出发也牵引出了一系列缺失的知识点。通过研读文章,基本了解了一些以前不明白的问题,所以整理了几篇,从几个角度介绍下Unicode, 并聊聊一些相关的问题。

roadmap.path

ASCII

上世纪60年代,美国人采用了一种编码来表示英语以及各种符号,该编码方式只有一个字节,能表示256(2^8)个字符。至今为止才定义了128个字符。包括33个控制字符和95个可显示字符,这些可显示字符涵盖了大小写英文字母和一些符号,这就是大名鼎鼎的ASCII编码

GB 2312

然而随着计算机的发展,各个国家的语言符号多不胜数,在中国光中文字符就有7000多个,还不包括繁体中文,ASCII显然无法满足这么多字符编码需求。所以中国人自己创造了一种字符编码,每个汉字和符号用两个字节来表示。第一个字节称为"高位字节",第二个字节称为"低位字节"。高位字节使用了0xA1 - 0xF7, "低位字节"使用了0xA1 - 0xFE。同时该编码方式兼容了ASCII的编码,对于小于127的字符即0x00 - 0x7F的字符予以保留。这种编码方式就是中文编码GB 2312

GBK

然而GB 2312能表示的文字也比较有限,对于一些人名,古汉语和繁体字也无能为力。所以我们改进了GB 2312的编码方式,扩展了GB 2312 中不使用的字节,使其同时包括了GB2312的所有内容,又新增了近20000个新的汉字,包括繁体字。该编码就是我们熟悉的GBK。后来由于又加了少数名族的文字,又推出了GB18030,用于取代GBK。而目前为止我们使用最广泛的中文编码还是GBK。

Unicode

再后来,由于不同的国家地区之间都使用不同的编码,导致计算机文件的读取都需要安装不同的解码软件。经常照成文件读取乱码。于是有一些组织决定制定出一个方案,通过统一的编码解决这个难题。于是其中一个团队发明了UCS编码,还有另一个团队发明了Unicode。后来两者达成一致,只发布一套字符集,那就是Unicode 。而UCS的码点将与Unicode保持一致。

Unicode最初规定用16位的编码空间,这16位编码空间称为统一码。这样理论上一共最多有2^16(65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。

目前的Unicode字符分为17组编排,每组称为一个平面(Plane),而每平面拥有65536(即2^16)个码点。上述16位统一码字符称为基本多文种平面(BMP),写成16进制就是从U+0000到U+FFFF。 剩下还有16个辅助平面(SMP),码点范围从U+010000一直到U+10FFFF。这17个平面结合起来至少需要占据21位的空间(2^16 x 2^5),也就是差不多3个字节(24位),而辅助平面实际上是用4个字节表示,方便以后向后扩展。

上面讲到的几种编码都是编码方式,规定了从码点到字符的映射关系,例如 Unicode中U+0061 对应的就是小写字母 "a", 我们可以在浏览器控制台中输入码点查找对应的字符:
roadmap.path

UTF
Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。

网页开发中比较熟悉和常用的编码实现是UTF-8。那么这种实现方式有什么优势呢?UTF-8是一种变长的编码方法。字符长度从1字节到4字节不等。最前面的128个字符,只使用1个字节表示,延续了ASCII的用法。其他分段的字节数如下:

roadmap.path

计算机在读取数据的时候都是从高位到地位或从地位到高位。当计算机读到一个3字节字符时,怎么判断是输出1位字符,还是继续读取接下来的2位并合并为一个字符呢?这就要涉及到UTF-8的具体实现了。

UTF-8是这样做的:

  1. 单字节的字符,字节的第一位设为0,对于英语文本,UTF-8码只占用一个字节,和ASCII码完全相同;

  2. n个字节的字符(n>1),第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足。

这样就形成了如下的UTF-8标记位:

0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

当读到第一位时,发现是0开头,就读一个字节。发现是110,就读两个字节,发现是1110就读三个字节,以此类推,再根据Unicode规则找到对应的符号输出。这种变长的编码方式,能根据字符采用不同位数的码点,能够有效减少文件的体积。

如果采用Unicode的编码方式直接作为实现方法。那么每个字符都是定长的码点,对于只需要一个字节的字符,需要在前面补0. 这样就照成了空间的浪费,文件就会变大。

UTF编码除了UTF-8,还有UTF-16:最小的码点为2个字节;UTF-32:每个码点固定用4个字节表示。由于UTF-32传输场进下会照成文件空间浪费,HTML5标准规定,网页不得编码成UTF-32。

关于Unicode的介绍就到这。UCS的相关知识,将在下一章结合JavaScript一起讲到。

参考文章:
https://zh.wikipedia.org/wiki
http://www.ruanyifeng.com/blo...
https://www.zhihu.com/questio...


fwon
2.5k 声望259 粉丝