在开发过程中,字符的编码问题经常让我们这些程序猿头疼不已,例如臭名昭著的“中文乱码”问题,我相信我不是唯一一个曾经“深受过其害”的人,因此我准备用几篇文章来总结一下字符编码相关方面的内容,力图做到通俗易懂,并以此希望能够给正处于水深火热中的XDJM们提供一点帮助。
首先是第一篇基本概念。本篇仅仅是粗略地讲解,因为字符编码这个话题很大,可以说它涉及到开发过程中的方方面面,要想细致深入是不可能的。幸运的是对于这次讨论的主题来说,我的目的是用一根绳子将字符编码所涉及到的知识点串起来,重点在原理上,至于繁枝末节的细节不会过多深入。
一、字符编码、字符集、字符传输编码
用一种特定的形式来表示字符的方法,就是字符编码。例如ASCII用0~127之间的整数来表示字符;Unicode将这个范围极大地扩展了,因此Unicode可以表示更多字符;还有莫斯码,这是一种使用“点”(dot)和“划”(dash)来表示字符或词组的编码,曾被广泛用于电报等无线电传输中。
在计算机中,字符通常用整数进行编码,根据不同的要求,一个字符可能需要一个或多个字节来表示。
字符集,或称字符编码集,是一个集合,其中的字符按照相同方式进行编码。例如ASCII、Unicode、莫斯码,等等。相同的编码在不同字符集中可能表示不同的字符。
字符传输编码也是一种字符的编码,主要是为了满足字符的传输、存储等实际应用的需要。字符传输编码基于某种字符集编码,但针对传输、存储等进行了优化,使其能够更好地满足实际应用。最常见到的字符传输编码可能要数UTF了,而UTF中最著名的又非UTF-8莫属。
二、设计一种字符编码的要点
字符编码的设计看似简单,好像只需将一堆字符按整数依次进行编号就完事了。但实际却完全相反,设计一个好的字符编码涉及到的和需要考虑的问题非常之多,绝不是简单的排列与组合。ASCII的专家组前后用了3年时间才出了第一个版本,第一个主要版本更是花费了7年时间。下面列举了一些在ASCII的设计过程中必须考虑的问题。
尽管在设计时需要考虑和权衡的细节如此之多,但令人惊讶的是,专家们当年的决策几乎都是最优的,直到今天,无数人直接或间接地从ASCII的简单、高效、方便等特点中获益。所以不得不感慨当年专家们的高瞻远瞩啊!
三、为什么需要字符传输编码
有多种原因使得一种字符集的原始编码不能很好地应用于传输、存储等过程中。这不只是技术或设计的原因。因此很多时候,还需要为某个字符集设计一套用于实际传输的编码方式。
现在谈到ASCII,可能我们都认为这是一种8位编码,即每个字符为一个字节。确实是这样。但在早期,ASCII码却是7位编码,所有字符的编码集中在0~127之间,因此每个字符的最高位总是0。因为在那个时候什么都贵,内存、网络、甚至硬盘(其实那时候还没有硬盘,只有纸带或软盘,存储容量非常有限),这些硬件的限制使得人们不得不最大限度地节约每一个字节,甚至每一位。所以虽然在设计上,ASCII是8位编码,但早期的很多系统和应用中都使用7位来传输,然后再通过程序对接收到的字节进行分割,每7位为一个字符,这样就能同时提高传输效率和节约存储空间。
另一个例子是UTF-8。UTF-8是可变长度的编码,每个Unicode字符被映射为1~4个字节的编码,其中前128个字符与ASCII完全相同,而每个汉字被编码为3个字节。UTF-8的这种特点使得它很适合编码英文字符占多数的内容,此时它的效率接近单字节编码,但如果通篇都是汉字,可能UTF-8并不是最佳的选择,此时它比原始的Unicode效率还要低。
字符集对其中的每个字符都指定了一个编码,但却不会规定应该如何用其进行传输或存储。因为这是与实际应用相关的,无法强制规定。最常见也是最著名的一个例子就是大小端问题或称字节序问题。简单来说,字节序问题就是当需要一次处理多个字节时,规定哪个字节在先哪个在后。
例如当程序读到“FF FE”时(16进制),如果按照读取时的先后顺序进行解释,这个数就是“FFFE”,如果以相反的顺序解释就成了“FEFF”。在这里,前一种方式就是大端,后一种是小端。不要在意细节,只需明白不同的解释会得到完全不同的结果。
我们熟知的X86及其兼容指令集系统采用小端方式,PowerPC处理器采用大端方式,包括TCP/IP在内的多数网络协议也是大端方式。
例如,采用UTF-16编码(一种双字节编码)的文本文件,其开头两个字节通常是“FF”和“FE”。在Unicode中,“FFFE”或“FEFF”都不代表任何有意义的字符,因此如果一个程序接收到或读取到了来自另一个程序或流中的字符序列时,发现开头是“FFFE”或“FEFF”,就知道这是一个采用UTF编码,并且采用了某种字节序——具体来说,如果开头是“FFFE”就是大端,另一种是小端(见前面的例子)。在文本文件或字符流中,像这种仅用于标记字节序的字符称为“BOM”,即Byte Order Mark。
注意:单字节编码没有大小端问题,只有多字节编码才可能会有此问题,因为单字节编码(例如ASCII和UTF-8)一次只需处理一个字节。
四、Unicode、UCS及UTF
这几个词容易让人迷惑,现在是时候来解释这个问题了。
Unicode是一种字符集,UTF是Unicode的一种具体的传输格式,这个前面已经讨论过了。
UCS在不同的场合下有多种意思:
在非严格的场合,人们通常会将ISO UCS标准和Unicode混为一谈,统称为Unicode,这也是UCS标准没有Unicode出名的原因。对于第一种情况,作为一个术语来讲时,也可以用Unicode来代表。所以一般情况下,用Unicode(或UTF)来代替UCS不会引起什么问题。
五、Windows中的代码页和ANSI编码
Windows中的代码页(Code Page)与其他系统的“字符编码”是一个意思,Windows用代码页来设置系统的默认编码(即本地化编码,Locale)。例如代码页936表示简体中文。代码页最早是IBM发明的,而不是微软。其实很多系统都有使用代码页,只是Windows中的最为人所熟知。
代码页使用数字来代表某种编码,例如在Windows中,代码页936即GBK(或GB2312)编码,65001代表UTF-8。
在Windows中我们会经常用到系统自带的记事本,当使用记事本的保存功能时,在编码一栏中通常默认的编码是“ANSI”。ANSI编码实际上就是当前代码页指定的编码,因此设置的代码页不同,ANSI代表的编码也不同。
六、为什么人们不能统一用一种编码呢,比如Unicode
当然这是一种美好的愿景,人们也正在努力地去实现。但目前还有很多问题没有解决。
在Unicode这样的统一字符集出现之前和之后,很多系统、程序、文件,甚至硬件中,都使用不同的字符集。大多数情况下,修改它们使其支持Unicode是不现实的。
对于英语国家的多数人来说,让其放弃使用ASCII这样的简单而高效,并且能够很好地满足他们的需求的编码而转用Unicode可能有些困难。即使是非英语国家,为了最大程度地实现与其他系统的兼容,尤其是那些已运转多年的老系统(这似乎又回到了上一条),人们可能还是会优先选择本地化的字符集。
Unicode只能解决编码问题,但实际应用中还有很多问题存在。例如字体,大部分字体只支持有限的字符,例如中文字体通常不会包含日文、俄文或已经消失的文字字符(而且通常也没有必要)。实际上,有成千上万种字体,但能够包含大多数Unicode字符的字体寥寥可数。
但并不是只有坏消息,好消息也是有的。比如人们也正变得越来越容易接受并习惯Unicode;并且越来越多的新系统、新技术、新标准都采用了Unicode。例如Windows、Linux、还有Java等等,W3C也在新的标准中鼓励人们使用Unicode。
七、总结
不要将思维局限在计算机中。广义上讲,在信息传递的过程中,编码是必须的,并且是无处不在的,例如语言、文字、音乐、绘画,都是编码。实际上,你说的话、写的字,其实质都是为了表达你的想法,因此它们都是对你的想法的不同形式的编码。
接下来的内容计划包含以下几个部分:
第2篇讨论编码中的一些细节,以及编码之间的转换和产生乱码问题的原因
第3篇:TODO