下载一个文档,一打开发现是乱码,不抓狂才怪…… 你们都知道,这都是字符编码闯的祸。ASCII、ANSI、GB18030、Unicode、UTF-8、UTF-8 with BOM、UTF without BOM、UTF-16、UTF-16LE、UTF-16BE…… 一大坨的谁分得清?听说UTF-8就是Unicode,但怎么Windows记事本里的保存选项有UTF-8和Unicode两个选项呀?!究竟各种软件是怎样判断一个文件是什么编码呢?为什么有时候又判断错误呢?让我一一道来。
世界上本没有字符编码。自从有了计算机,我们有了用0和1记录文字的需求,于是字符就有了编码。
== ASCII ==
ASCII编码表示的“Hello GuoKr”(十进制):72 101 108 111 32 71 117 111 75 114
ASCII是最基本的编码,它定义了0~127对应的字符,包括最基本的英文字母、标点符号。它无法表示中文。ASCII编码的文本,每一个字节都是0~127,如果某个字节大于127,那它一定不是ASCII编码。
== GB* / ANSI ==
为了用计算机记录并显示中文,中国人发明了GB系列编码。GB系列编码定义了中文汉字、标点的编码。按照GB系列编码,在一段文本中,如果一个字节是0~127,那么这个字节的含义同ASCII编码,否则,这个字节和下一个字节共同组成汉字(或是GB编码定义的其他字符)。因此,GB系列编码向下兼容ASCII,也就是说,如果一段用GB编码文本里的所有字符都在ASCII中有定义,那么这段编码和ASCII编码完全一样。GB编码早期收录的汉字不足一万个,基本满足日常使用需求,但不包含一些生僻的字,后来在一个个新版本中加进去。最早的GB编码是GB2312,后来有GBK,最新的是GB18030,加入了一些国内少数民族的文字,一些生僻字被编到4个字节,每扩展一次都完全保留之前版本的编码,所以每个新版本都向下兼容。
同样,日文、韩文、世界各国文字都有了它们各自的编码(如果ASCII不能满足使用要求的话)。这些编码都和GB编码相似,兼容ASCII并用两个字节表示一个字。所有这些各国文字编码,微软统称为ANSI 。所以即使知道是ANSI,我们还需要知道这是哪国文字才能解码,因为这些编码都互相冲突。另外,你无法用一段ANSI 编码表示既有汉字、又有韩字的文本。
等等,上面我误导大家了……其实是微软误导大家了,严格来说ANSI 不是字符编码,而是美国一个非营利组织,他们做了很多标准制定工作,包括C语言规范ANSI C,还有各国文字编码对应的“代码页”(code page)。ANSI 规定简体中文GB编码的代码页是936,所以GB编码又叫做ANSI code page 936(按ANSI标准的代码页936),各国编码被统称为ANSI 由来于此——这是个离谱的历史错误,这就像我自己给国内的大学做了个排名,排名的依据是饭堂吃出虫子的概率,然后国内的大学就被统称为黄油猫。不过对于这些乱七八糟互相冲突的多国字符编码,有个统称还是不错的(我了个去还要谢谢微软),下面讨论姑且继续使用ANSI 。
==Unicode / UTF / UCS==
为了解决冲突的问题,促进世界和平,我们有了Unicode(联合码?)——一种将全世界所有语言所有字符都收录在一起、让它们和平共处的编码……哦,等等,Unicode虽是一种字符编码,但严格来说它和GB18030不能相提并论:它只定义了每一个字符对应一个整数(目前包含了十万多个字符,其中0~127和ASCII完全一样),但它没有定义这个整数如何变成字节。当你告诉我这段数据是Unicode编码,啊,不好意思,我还是不知道该怎么解码——因为变成字节流的格式不只一种,它们都叫做“Unicode转换格式”(Unicode Transformation Format,简称为UTF)。
所以这里面就有了一大堆UTF:UTF-8、UTF-8 with BOM、UTF-8 without BOM、UTF-16、UTF-16LE、UTF-16BE…… 还有很少见的UTF-32、,早期还会听说过UCS-2、UCS-4…… _(:з」∠)_
等等,为什么Windows记事本里有个保存选项是Unicode?这个稍后说。
先说最常见的UTF-8:它将一个字符编为1-4个字节,其中一个字节的字符和ASCII 完全一致,所以它也向下兼容ASCII。和ANSI类似,UTF-8第一个字节决定了之后多少个字节是一组好基友。多数汉字在UTF-8里为3个字节,有一些生僻的汉字会编到4字节。
我们迎来第一种不兼容ASCII的编码:UTF-16。UTF-16以每2个字节为一个单元,每个字符由1-2个单元组成,所以每个字符可能是2个字节或者4个字节,包括最常见的英文字母都会编成两个字节。大部分汉字也是2个字节,少部分生僻字为4个字节。UTF-16还有讲究,一个单元中的两个字节的顺序不是唯一的。学过计算机原理的同学知道,计算机中表示一个整数分两种格式:低位在前高位在后,或者反过来。例如用两个字节表示260这个整数,可能是:
低位在前:04 01 (260=4+256*1)
高位在前:01 04 (260=256*1+4)
低位在前的UTF-16叫UTF-16LE,高位在前的叫UTF-16BE。目前绝大部分的计算机系统都使用低位在前的整数格式,所以如果没有声明,UTF-16默认是LE。
早期Unicode收编的字还不多时,两个字节足够表示所有字符,所以有一种固定为两个字节的UTF,叫UCS-2。UTF-16的两个字节部分和UCS-2完全一样,所以UTF-16向下兼容UCS-2。UCS-2同样分LE和BE。而Windows的记事本还有Windows其它地方所谓的Unicode,当代的Windows里其实是UTF-16LE,在Windows XP和更早的版本里是UCS-2LE。微软(又是微软)正是混淆Unicode概念的祸首,微软你这么讨厌你家人知道吗?
此外,UTF-32和UCS-4固定为4个字节一个字符,同样分LE和BE。
还没完,这么多字符编码,软件打开时如何知道是哪个编码?于是不知道谁蹲坑时想出了BOM:在一个文本文件或者一段字符编码前加上几个固定的字节用于识别,这些字节保证不对应任何一个字符,所以软件一读就能验明正身:
EF BB BF - 我是UTF-8
FF FE - 我是UTF-16LE
FE FF - 我是UTF-16BE
(没有BOM,直奔主题)-你猜?
不错,没BOM只能靠猜了。软件读入文件时可以所有编码都试一下,看哪个像。另外,BOM只针对Unicode系列编码,ANSI通通不会有BOM。很显然,没有BOM难免偶然猜错。网上就流传了一个神奇的段子:打开Windows记事本,打入“联通”两个字,保存,关闭,再打开,变成了个黑块。记事本用ANSI(GB18030)保存联通这两个字,刚好这两个字的GB18030编码看起来很像UTF-16,于是就当成UTF-16来打开……
BOM听起来很不错,但实际是个讨厌的设计,因为它和很多协议、规范不兼容,这是题外话。
于是,UTF-8 with BOM、UTF-16 without BOM 你们就懂了。等等,如果不提BOM,究竟有BOM还是没有BOM?—— 又是一个十分纠结的问题,Windows里的软件一般都默认有BOM,而其它系统都默认没有BOM——可能是因为Windows常要兼容ANSI的原因,特别依赖BOM来防止会错意。
==谁是正统==
这么多种编码,用来写文章就罢了,打开个文档看到乱码就退出、换个程序或者换个编码再打开;但写程序时可半点马虎不得,各种程序开发环境对编码支持都不一样,如果编码没搞好,你写好的程序可能在别人计算机上就运行不起来了。如果我开发跨平台的代码,而且要有中文(注释),哪用什么编码好?以下是我所知道各开发环境/编译器支持常见编码情况(所有=ANSI、UTF-8 BOM and no BOM、UTF-16LE BOM and no BOM):
- Visual Studio:所有,保存默认ANSI 。
- VC编译器:所有,除了UTF-8 without BOM(直接当成是ANSI )
- Windows记事本:所有,保存默认ANSI,无法保存无BOM。
- XCode:只支持 UTF-8 without BOM。
- GCC:所有
- vim:所有,保存默认为系统默认编码,一般是UTF-8 without BOM。
- Eclipse:所有,保存默认不明,Mac下居然是ANSI (我们中出了个叛徒)。
ANSI是无法跨境的,用GB写的文档拿去韩国就果断乱码了。光是简体中文系统,ANSI 也是经常被认错的,Eclipse里经常(不总是)出现打开ANSI 文件是乱码的情况,这是因为ANSI 没有很明显的特征。XCode和Mac的文本编辑器打开ANSI 直接是乱码,因为明确不支持。ANSI 容错性普遍比较差,一个字节错了可能导致后面的字通通挂掉。为了防止Eclipse等发神经,也为免跨国带来麻烦,更为了你自己的数据安全,请远离ANSI。
UTF-16不兼容ASCII,不兼容C语言的字符串处理库函数(因为字节流里有\0),除了Windows爱用,其它系统都痛恨它。BOM和很多协议规范冲突,很多软件都抵制,也是只有Windows常用,而且将其列为正统(作反)。
综上,跨平台开发请使用UTF-8 without BOM,那是最通用的编码,是很多软件系统的默认编码,你在看的网页也用它。它特征明显,除了VC编译器和微软的各种软件外暂时没发现哪个软件会有认错的情况。它还有经过精心设计的容错机制,错一个字节最多只会错一个字符。请手动设置你的开发环境,将默认保存的编码设为UTF-8 without BOM,并将其它编码的文件转换过来,乱码就拜拜啦;记事本等不支持的编辑器,不要让他们摸你的文件。至于VC编译器硬要闹别扭就由它去吧 _(:з」∠)_
(经过测试,VS2010能正常打开UTF-8 without BOM的代码,但可能编译不通过,并报奇怪的编译错误)
为什么程序猿那么喜欢黑微软啊?因为微软那群闷骚的设计师总是和大家不太一样……
写在最后:
在这个机器能听懂人语的年代,我们还要操心字符编码这么低级的事,真是不可思议…… 解决方案这么简单:几家大公司坐一圈,说定一种编码,以后通通用一种编码。而且这种编码已经浮出水面、支持成熟:UTF-8。我很赞赏苹果,他们果断和ANSI还有一大堆不统一的编码一刀两段,所有软件只支持UTF-8,这才是真正对用户负责 —— 而微软到今天还死抱着ANSI不放,在这件小事上就看出他就是如此不舍得过去一点的成就,如此不舍得革自己的命。
转载:http://www.guokr.com/blog/763017/