计算机字符编码
什么是编码
编码是信息从一种形式或格式转换为另一种形式的过程;解码则是编码的逆过程。
举个栗子,你和你的朋友同坐在一间教室里,中间隔着好几个人,你想通过中间的人传递纸条,告诉你的朋友一条信息“abcdefg”,但是你又不想别人知道信息的内容,这时候你就需要将信息的内容转换下,怎么转换?假设你和你的朋友以前有过约定,你们在纸条中传递内容的所有英文字母都要向后偏移一位,即a转换为b,c转换为d,z是英文字母中的最后一位,则转换为第一个字母,即a。如此,你将在纸条上写下“bcdefgh”,信息从“abcdefg”转换为“bcdefgh”的过程就是编码,所采用的编码方式是英文字母向后偏移一位,你的朋友拿到编码后的信息,再依次将字母前移一位,得到真实的信息,这是编码的逆过程,即解码。
计算机中的字符为什么需要编码
上面提到的例子需要编码是出于安全考虑,那计算机中的字符为什么也需要编码,是出于同样的目的吗?当然不,计算机中的字符之所以需要经过编码,归根结底是因为计算机中的所有数据只能用二进制表示,即0和1两种数值,想要在计算机系统中对我们所认知的字符加以区分,就需要使用0和1的排列来为其编码。
ASCII
American Standard Code for Information Interchange,美国信息交换标准代码,这是最通用的信息交换标准,通用到什么程度?后边我们会了解到现代计算机系统中存在很多的字符编码,但这些编码几乎所有都兼容ASCII,即在这些兼容ASCII的字符编码中,对于ASCII可以表示的字符,编码结果保持一致。那ASCII可以表示哪些字符?
ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符,其中有95个可显示字符,即可通过打印或是显示器展示出来,剩下的33个是控制字符或通信专用字符,如LF(换行)、CR(回车)、ACK(确认,通信专用字符)等
在ASCII中,所有字符使用7个比特位来存储,我们知道计算机系统存储单位为一个字节,通常为8个比特,剩下的1个比特位保持为0。如字符“A”的ASCII码十进制值为65,二进制为01000001,更多字符参考ASCII编码对照表。
ASCII扩展
ASCII有其局限性,只能显示26个基本拉丁字母、阿拉伯数字和英式标点符号,仅适用于现代美国英语。对于其它同属印欧语系的语言来说,还有部分字符无法使用ASCII编码,怎么解决这个问题?我们之前提到过,ASCII仅使用一个字节中的7个比特位,还剩下一个比特位没有使用,通过排列知识我们可以算出,2^8 = 256,如果使用8个比特位,那我们将可以对256个字符进行编码,排除掉ASCII中原有的128个字符,还多出来128个空位,这已经完全足够其它印欧语系的语言使用了,如ISO 8859-1编码。
印欧语系的问题解决了,那我们汉藏语系怎么办,就拿中文来看,单就中国大陆常用字就有2500个,另外还有大量的繁体字和生僻字,我们的问题怎么解决?
中文编码
显然只用1个字节来存储编码已经不能满足我们的需求了,我们必须要考虑使用多个字节来表示一个汉字。GB2312-80,也叫作GB2312,是由中国国家标准总局1980年发布的一套国家标准,适用于计算机系统中汉字的处理和信息交换,这套标准便是用两个字节来存储汉字编码。
使用两个字节来存储编码有一个问题,如何兼容ASCII?我们可不可以直接抛弃ASCII,将ASCII中原有的128个字符重新编码为两个字节的编码?当然是可以的,但这样做未免有些奢侈,原本一个字节就足够表示,强行转化为两个字节最终可能会导致占用的存储空间大上许多,而且即使仅用美式英语中的128个字符也无法与其它使用ASCII编码的计算机进行通信,因为双方对于这128个字符的编码不同。
看来解决与ASCII编码的兼容问题势在必行了,我们来看看GB2312是怎么做的。
GB2312使用7位双字节编码,共收录汉字6763个和非汉字图形字符682个。它是怎么来表示这么些汉字字符的呢?由于使用到了7位双字节,我们知道7位可表示的字符编码数为128个,将这两个字节可表示的字符编码分别作为二维坐标系的横纵坐标轴,那我们就可以看到一个可以容纳128×128=16384个字符编码的平面。当然实际可存储的数量没有这么多,要排除掉ASCII中的控制字符0~31,空格符32,删除符127,剩下的也就只有94位,所以GB2312使用94×94的区位码来表示字符。
除了区位码,还有一个概念叫国标码,GB2312中字符的国标码等于十进制区位码分别加上32,具体为什么是32,猜测是想保留ASCII中的32个控制符,只是这样还是没有与ASCII兼容,所以又有机内码的概念,机内码=十六进制国标码+8080H,即两个字节各自加上80H,相当于偏移128个字符编码的位置,我们知道ASCII可表示的字符只有128位,这样就能实现完全兼容ASCII编码了,解码的时候也很容易判断,因为ASCII编码最高位为0,而GB2312偏移后所有编码最高位为1,如此便能完美区分开来。
ANSI
当我们在windows记事本程序中编辑完一篇文档并保存时可以设置该文档的编码,列出的编码格式中有一种叫ANSI,这是什么东西?严格来说ANSI并非是一种字符编码集,它在不同语言的windows系统中表示为不同的字符编码集,只存在于windows操作系统上,并且以windows code page所指定的值来决定它具体表示哪种字符编码集,如在简体中文系统下,windows code page的值为936,对应的编码是GBK(扩展于GB2312字符编码),而在繁体中文系统下,code page值为950,对应Big-5编码。
Unicode统一码
有了中文编码,现在已经可以在计算机上处理和传输中文字符了,可是问题还没完,GB2312编码只能保证部分中英文字符可以得到有效处理,如果再涉及到一些其他国家的语言,GB2312就有些无能为力,这时候就该Unicode出场了。
Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。
Unicode使用2个字节来存储字符编码,也叫作UCS-2,考虑到以后2个字节可能不够用,所以还有一个UCS-4,即使用4个字节表示一个字符。
这里需要注意的是Unicode只是一种编码方案,它规定的只是每个字符对应的二进制编码,至于这些编码在具体的计算机系统中要如何去存储,则由具体字符编码集实现。
UTF-8
UTF-8就是Unicode的一种具体实现,它怎么存储Unicode字符编码?
以汉字“中”举例,它对应的Unicode编码为4E2D,二进制表示为01001110 00101101,在UTF-8中存储为 11100100 10111000 10101101,十六进制E4B8AD,可以看到原本的Unicode编码占用了两个字节的长度,但在UTF-8编码中存储时却用到了3个字节,为什么?还是那个原因,为了兼容ASCII编码。我们知道ASCII编码是使用一个字节来存储字符编码的,并且最高位为0,因此UTF-8采用了可变长度字符编码存储方式,规则如下:
单字节字符,字节第一位设为0,与ASCII编码完全保持一致。
多字节字符,假设字符会占用n个字节,需要将第一个字节的前n位设为1,紧接着的一位设为0,其余字节前两位皆要设为10,而后所有字节剩下的空位用来依次存放字符的Unicode编码,高位用0补足。
例如上面提到的“中”的UTF-8编码为11100100 10111000 10101101,因为这个字符占用3个字节,所以第一个字节前3位为1,第四位则为0,随后第二个字节与第三个字节都以10开头,即1110XXXX 10XXXXXX 10XXXXX。“中”的Unicode编码为01001110 00101101,去除掉高位的0后,一共15位,而存储位置有16位,因此我们将最高位设为0,即11100XXX 10XXXXXX 10XXXXXX,将Unicode编码依次放入,就可以得到最终的UTF-8编码。