小论计算机Encoding

image.png

乱码问题一直是最让人心烦的一类问题,因为不知从何而来,由何而起,只能一处一处的改编码,运气好了就改成功了,运气不好就继续试,有时候明明把所有能改的地方全改成UTF-8了,却仍然无济于事,着实叫人抓狂。这一切都要从1946年的那个春天说起。 在第一计算机台诞生的时候,那个时候还只有0和1,慢慢的,科学家们厌倦了只有0和1的世界,所以他们就聚在一起开了个会,讨论了讨论,决定按特定的方式将人类语言映射到0和1的序列上,于是他们定了个标准,规定大家都这么干,叫美国标准信息码,就是我们熟知的ASCII。可是天真的美帝国主义,以为计算机只会存在于英语世界中,也没想过我们社会主义程序员也能有用上计算机的一天,所以他们的ASCII码里就没有我们中文,但这是后话了,因为还有一拨人比我们先用上计算机——欧洲程序员。因为欧洲有很多像希腊字母之类的非英文字母,所以他们为了能让整个欧洲正常用上计算机,所以就也弄了一套标准,叫:ISO-xxxx。 >ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。其中:

ISO 8859-1编码启用了从128到255的字符,被称为”扩展字符集”。属于单字节编码,应用于拉丁文系列。显然,ISO 8859-1最多能表示的字符范围是0-255(编码范围是0x00-0xFF),其中0x00-0x7F之间完全和ASCII一致,因此向下兼容ASCII。除ASCII收录的字符外,ISO-8859-1收录的字符还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号等出现的比较晚,没有被收录在ISO 8859-1当中。

后来随着计算机在世界的普及度越来越高,越来越多地区的人们想要用自己的语言使用计算机,所以就诞生了各种各样的编码方式,其中就包括我们中国的GB系列国家标准信息交换码。 >GB系列编码定义了中文汉字、标点的编码。按照GB系列编码,在一段文本中,如果一个字节是0~127,那么这个字节的含义同ASCII编码,否则,这个字节和下一个字节共同组成汉字(或是GB编码定义的其他字符)。因此,GB系列编码向下兼容ASCII,也就是说,如果一段用GB编码文本里的所有字符都在ASCII中有定义,那么这段编码和ASCII编码完全一样。GB编码早期收录的汉字不足一万个,基本满足日常使用需求,但不包含一些生僻的字,后来在一个个新版本中加进去。最早的GB编码是GB2312,后来有GBK,最新的是GB18030,加入了一些国内少数民族的文字,一些生僻字被编到4个字节,每扩展一次都完全保留之前版本的编码,所以每个新版本都向下兼容。

此时,终于有有识之士意识到了混乱的编码给人们网上冲浪带来的不便以及程序bug,所以就有人想用一种编码表示全世界所有的文字,然后就有了Unicode。 >Unicode是Unicode.org制定的编码标准,目前得到了绝大部分操作系统和编程语言的支持。Unicode.org官方对Unicode的定义是:Unicode provides a unique number for every character。可见,Unicode所做的是为每个字符定义了一个相应的数字表示。比如,”a”的Unicode值是0x0061,“一”的Unicde值是0x4E00,这是最简单的情况,每个字符用2个字节表示。 >Unicode是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(如果考虑辅助平面,也有四字节的)编码,包括英文字母在内,都以双字节表示,所以它是不兼容ISO 8859-1编码的。不过,相对于ISO 8859-1中所编码的字符来说,Unicode编码只是在前面增加了一个全0字节,例如字母a的Unicode编码为”00 61”。和GB2312/GBK等非定长编码相比,定长编码便于计算机处理,而Unicode又可以用来表示所有字符,所以在很多软件内部是使用Unicode编码来处理的,比如java。 >Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符. Unicode的编码空间可以划分为17个平面(plane),每个平面包含216(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF, 其中xx表示十六进制值从00(16) 到10(16),共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),码位从U+0000至U+FFFF,包含了最常用的字符。其他平面称为辅助平面(Supplementary Planes)。 >对于在Unicode基本多语言平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存,但是从U+D800到U+DFFF之间的码位区段是永久保留不映射到任何Unicode字符的。而在辅助平面定义的字符,即从U+010000到U+10FFFF的码位,则(UTF-16的做法是)以代理对(surrogate pair)的形式,将其拆分成两个2字节(位于0xD800-0xDFFF区段)共4字节的值来储存。进行代理对映射的方法本文就不深入讨论了,有兴趣的可以自行搜索。

但是Unicode占用空间确实有点大,比如说每个英文字符要占用两个字节,但第一个字节其实全部都是零,所以大家就又对Unicode继续编码,然后UTF系列编码方式就诞生了,常见的有utf-8、utf-16等。 >UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下: >Unicode编码(十六进制)  >UTF-8 字节流(二进制) >000000-00007F >0xxxxxxx >000080-0007FF >110xxxxx 10xxxxxx >000800-00FFFF >1110xxxx 10xxxxxx 10xxxxxx >010000-1FFFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx >UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是6个字节。从上表可以看出,6字节模板有31个x,即可以容纳31位二进制数字。Unicode的最大码位0x7FFFFFFF也只有31位。 > >

UTF-16编码以16位无符号整数为单位。我们把Unicode unicode 编码记作U。编码规则如下: 如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。 如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

随着将来网络带宽和存储容量的提升,大家都无所谓的用上了万国码,可能以后的程序员们就只能在历史书中见到困扰了他们先辈数十年的乱码问题了吧。