写程序的人基本上都会遇到乱码的问题,之前自己对字符集、编码等问题也是一知半解,大概明白什么意思,但却说不清楚。由于公司需要做多语言,于是研究了一下,终于把字符集和编码等问题弄明白了。
ascii、GB2312、GBK、unicode、utf-8、utf-16、ucs2、ucs4......,对于很多人来说这些东西都是比较模糊的(以前的我也是),字符集编码问题不理解透彻,很难说清楚他们之间的关系。下面就从头开始把这些概念整理一下,希望对大家有帮助,自己也总结一下。
计算机只认识0和1,因此世界上的任何符号在计算机中都必须转换成0和1来表示,所谓字符集就是一个字符对应到数字编码的对应表。于是最先有了ascii 码,它是用一个字节(8位)来表示字符。ascii的第一个bit永远是0,因此ascii码最多能表示128个字符(2的7次方)。英语大小写字母共 52个字母,加上数字和一些控制符号(如回车、tab等),128也够用了。
但随着计算机的普及,除了英语以外的其他语言(如中文)使用ascii码就不行了。于是每个国家都为自己的语言定义了一套字符集,以中文为例,有了 gb2312、big5等字符集。gb2312中收录了7000多个常用的简体中文字符,而big5为台湾用的繁体中文。gb2312和big5等字符集都是用两个字节来保存的,因为一个字节只能表示128个字符。新的字符集出来,程序问题也相应的出来了,以前的程序处理字符都是1个1个字节的处理字符,而新的字符集要求两个两个的处理字符,那我们的程序到底是该一个一个字节读取还是两个两个字节的读取呢?很快人们发现ascii码都是以0开头,那么新的字符集都用1开头问题就解决了。程序读到以0开头的字节就一个字节一个字节的读字符,遇到1开头的字节就两个两个字节的开始读。因此gb2312、 big5等字符集和ascii是兼容的。
gb2312只收录了7000多个字符,并没有收录所有的中文字符,因此在1995年和2000年,我国先后推出了gbk1.0和gb18030。 gb18030收录了所有的中文字符,包括少数民族的文字。到此我们有了一个中文字符集的发展线路:ascii -> gb2312 -> gbk1.0 -> gb18030 他们是由小到大的,并且是向下兼容的。到此中文的问题解决了,似乎一切都OK了。
但世界上如此多的国家,每个国家都有一套自己的字符集,这样太乱了,于是老大哥ISO开始推出统一全世界字符的字符集了——unicode(也称 UCS)。unicode占用4个字节,总共可以收录2147483648个字符,这足以涵盖地球上所有用到的字符了。但一个字符4个字节相当的浪费资源,特别是在网络传输时。于是unicode推出了2个标准,分别是UCS-2和UCS-4。
USC-2用2个字节保存字符,其包含了西欧和亚洲绝大多数国家的字符,常用unicode采用USC-2。USC-4用4个字节保存字符,这种基本上很少用到,因为太浪费资源。
UTF-8、UTF-16是unicode的编码格式,这里需要搞清楚字符集和编码格式的区别。字符集是一个字符和数字的对应表,表示每一个字符对应的数字,而编码是指这些字符对应的数字在计算机中如何保存。比如,字符“中”对应的unicode码为4E2D,但在计算机中保存时不一定就是4E2D。(注意,此处写的是不一定)
先说简单的UTF-16,UTF-16用固定两个字节对unicode进行编码,因此UTF-16编码就等于unicode码。例如,字符“中”对应 UTF-16编码为4E2D。这中间又必须考虑字节序的问题,因为不同的平台对于字节序的处理方式不一样,有的是高位在前低位在后,而有的正好相反。因此,字符“中”的UTF-16有两种编码方式,分别是4E2D和2D4E。那程序如何知道是哪种呢?于是有了BOM( Bill Of Material),简单点说就是在文件最前面加一个标记(占2个字节,其实也是一个unicode字符)来表示高位在前还是低位在前。如果文件最前面是FEFF则表示高位在前,又叫Big-Endian,如果是FFFE则表示低位在前,又叫Little-Endian。
UTF-8比较麻烦一点,他是编码是变长的,也就是说不是使用固定的两个字节来进行编码。对于0-127的字符采用0XXXXXXX的形式保存(1个字节),128-2047采用110XXXXX 10XXXXXX的形式保存(两个字节),2048-65535采用1110XXXX 10XXXXXX 10XXXXXX(三个字节)的形式保存。举例来说,字符“中”对应的unicode码为4E2D(0100111000101101)也就是 20013,在2048-65535之间,因此“中”的UTF-8编码为11100100 10 111000 10 101101。对于UTF-8编码来说,程序读到0开头的字节表示只需要读一个字节,遇到110开头的表示需要读取两个字节,而读到1110开头的表示要读取三个字节。因此对于有大量英文字符的文档而言,使用UTF-8编码可以节约大量磁盘空间。UTF-8是不需要BOM的,因为它是单个字节处理的,但也可以为UTF-8文件加上BOM。为UTF-8加上BOM后,在文件头会多出3个字节,为 EF BB BF,它就是FEFF对应的UTF-8编码。
OK,总结一下吧,ascii、gb2312、gbk、big5、unicode都称为字符集,而UTF-8、UTF-16叫做编码方式(其实 gb2312也有其编码方式,此文暂不讨论)。一般情况下,尽量使用UTF-8编码方式,因为它即通用又能很好的节约空间。
我们可以做一些实验检验一下上文所述内容,下面是我检验的结果,大家可以对照一下:
文本内容 | 文本格式 | 占用空间大小 |
a | ascii | 1字节 |
a | unicode | 4字节 (FFFE + a字母) |
a | unicode big endian | 4字节 (FEFF + a字母) |
a | utf-8 | 1字节 |
a | utf-8+ | 4字节 (EFBBBF + a字母) |
中 | ascii | 2字节 |
中 | unicode | 4字节 |
中 | unicode big endian | 4字节 |
中 | utf-8 | 3字节 |
中 | utf-8+ | 6字节 |