漫谈计算机编码:从ASCII码到UTF-8
第一阶段 盘古开天辟地——ASCII码
计算机大家都知道,本质是二进制运算和存储。在计算机中人类的几乎所有文字和字符都没法直接表示,所以美国人在发明计算机的时候为了让计算机可以用于保存和传输文字,就发明了ASCII码(American Standard Code for Information Interchange,美国信息交换标准代码),用128个数字分别映射到美国人常用的一些字符,包括阿拉伯数字和26个英文字母的大小写,以及其他一些常用的符号。一个字符占用7位二进制,用一个字节byte(8位)来存储。
这样一来就满足了美国人的交流需要。
第二阶段 军阀混战之战国七雄——GBK
但是计算机这东西真的是太好用了,只在美国使用有点暴殄天物,其他国家为了能让计算机在自己国家也好用,就发明了自己的编码系统来适配自己国家的文字和字符,比如西欧国家的Windows-1252和中国的GBK以及港澳台地区用得比较多的Big5。
因为ASCII只有128个,用二进制7位就够了,而计算机的最小存储单位byte是8位,所以ASCII码中每个字节的最高位永远是0,因此这些国家在搞自己的编码的时候很容易地就保持了对ASCII的兼容,即当每个字节最低位为0的时候表示ASCII码,为1的时候表示自己国家的编码。
以中文GBK为例,GBK使用固定的两个字节,当解析到的字节最高位为0时,只解析当前字节作为ASCII码,当最高位为1的时候,就将下一个字节读进来一起解析为一个汉字,而不用考虑第二个字节的最高位,然后跳到第三个字节继续解析,往复如此直到解析完成。
其他国家和地区的编码系统也大致如此。
第三阶段 秦王扫六合之书同文——Unicode
现在问题来了,每个国家在设计自己国家的编码的时候基本上都只考虑了自己国家的需求,导致不同国家之间的编码互不兼容,这给不同国家之间的交流造成了很大的阻碍和困扰。
这时候就需要有人来干秦始皇干过的“车同轨、书同文”的事了,于是就出现了Unicode。Unicode为世界上所有字符设定了统一并且唯一的数字序号,这个编号范围从0x000000到0x10FFFF,包括110多万。也就是说,世界上所有国家的文字和字符都能在Unicode中找到唯一的序号,这样一来大家就有了统一编码标准。
第四阶段 小篆到简体中文——UTF-8
但是,Unicode只规定了编号,没有规定编号到二进制的映射方式。
我们先做最简单的设想:在计算机中使用固定数量的字节来存储Unicode,即直接存储所有的字符编号的二进制编码。但这样的话对于编号比较小的那些字符来讲高位全都是0,非常浪费空间,再考虑到最常用的字符编号都比较小,这么做的话对于网络传输速度和存储空间都是极大的浪费,所以我们需要一种变长的Unicode映射方式来节省空间,于是就出现了UTF-8。
UTF-8就是使用变长字节表示,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数从1到4个不等。这样一来存储同样多的信息需要的内存空间就便少了很多,在网络通信中获取信息的效率也会提升很多。
UTF-8的优势如此明显,所以它很快就席卷了全球,成为了大家最常用的编码。
一点补充:
UTF-8编码中,大多数中文占用的字节数是3个,相较于GBK的2个,多出了50%,这也就意味着在存储纯中文文档的时候,UTF-8编码的文件占用空间是GBK编码文件的1.5倍。
基于上面这项特性,GBK在很多纯中文系统中还是有很大的应用市场。其目的呢,就是为了节省用户的硬盘和流量
两点补充(关于java中的char):
Unicode给世界上每个字符分配了一个编号,编号范围从0x000000到0x10FFFF。编号范围在0x0000到0xFFFF之间的字符,为常用字符集,称BMP(Basic Multilingual Plane)字符。编号范围在0x10000到0x10FFFF之间的字符叫做增补字符(supplementary character)。
Unicode主要规定了编号,但没有规定如何把编号映射为二进制,UTF-16是一种编码方式,或者叫映射方式,它将编号映射为两个或四个字节,对BMP字符,它直接用两个字节表示,对于增补字符,使用四个字节,前两个字节叫高代理项(high surrogate),范围从0xD800到0xDBFF,后两个字节叫低代理项(low surrogate),范围从0xDC00到0xDFFF,UTF-16定义了一个公式,可以将编号与四字节表示进行相互转换。
Java内部采用UTF-16编码,char表示一个字符,但只能表示BMP中的字符,对于增补字符,需要使用两个char表示,一个表示高代理项,一个表示低代理项。
使用int可以表示任意一个Unicode字符,低21位表示Unicode编号,高11位设为0。整数编号在Unicode中一般称为代码点(Code Point),表示一个Unicode字符,与之相对,还有一个词代码单元(Code Unit)表示一个char。
java中char的包装类Character就封装了很多与此有关的静态方法,搞清楚这些定义有助于我们更好地理解和使用Character