原文地址http://www.joelonsoftware.com/articles/Unicode.html
作者:Joel Spolsky
译文:http://local.joelonsoftware.com/wiki/Talk:Chinese_(Simplified)

每个程序员都绝对必须知道的关于字符集和Unicode的那点儿事(别找借口!)

Unicode与字符集

你曾经是否觉得HTML中的"Content-Type"标签充满神秘?虽然你知道这个东西必须出现在HTML中,但对于它到底干吗你可能一无所知。

你是否曾经收到过来自你保加利亚朋友的邮件,到处都是"???? ?????? ??? ????"?

我很失望,因为我发现许多软件开发人员到现在为止都还没有对字符集、编码、Unicode有一个清晰的认识,这是个事实。几年前,在测试FogBUGZ项目时,忽然想看看它能不能接收用日文写的电子邮件。这个世界上会有人用日文写电子邮件?我不知道。测试结果很糟糕。我仔细看了用来解析MIME (Multipurpose Internet Mail Extenisons)格式的邮件所用的ActiveX控件,发现了它在字符集上面做的蠢事。于是我们不得不重新写一段代码,先消除Active控件的错误,然后再完成正确的转换。类似的事情在我研究另一个商业库的时候同样发生了,这个库关于字符编码这部分的实现简直糟透了。我找到它的开发者,把存在问题的包指给他,他却表示对于此无能为力。像很多程序员一样,他只希望这个缺陷会被人们遗忘。

事实并非如他所愿。因为我发现,像PHP这么流行的网页开发工具,竟然在实现上也完全忽略了多种字符编码的存在(译者注:这篇文章写于2003年,现在的 PHP可能已经纠正了这个问题吧),盲目地只使用8个比特来表示字符,于是开发优秀的国际化的Web应用程序变成了一场梦。我想说,受够了。

我申明:在2003年,如果你是一个程序员,但你却对字符、字符集、编码和Unicode一无所知,那么你别让我抓到你。如果落在我手里,我会让你待在潜水艇里剥六个月的洋葱,我发誓。

另外,还有一件事:

这个一点都不难。

在这篇文章里,我所讲的是每一个工作中的程序员都应该知道的知识。所有以为"纯文本 = ASCII码 = 一个字符就是8比特"的人不单单错了,而且错得离谱。如果你仍然坚持使用这种方式编写程序,那么你比一个不相信细菌的存在医生好不到哪里去。所以在你读完这篇文章以前,不要再写半行代码。

在我开始之前,必须说明白,如果你已经了解了国际化,可能你会觉得这篇文章过于简单。没错,我的的确确是想架一座最短的桥,让任何人都可以理解发生了什么事,懂得如何写出可以在非英文语言环境是正常工作的代码。还得指出,字符处理仅仅是软件国际化中的一小部分,但一口吃不成个胖子,今天我们只看什么是字符集。

历史回顾

可能你以为我要开始谈非常古老的字符集如EBCDIC之类的,实际上我不会。EBCDIC与你的生活无关,我们不需要回到那么远。

关于字符集和Unicode的相关知识

回到一般远就行了。当Unix刚出来的时候,K&R写了《The C Programming Language》一书,那时一切都很简单。EBCDIC已经惭惭不用,因为需要表示的字符只有那些不带重音的英文字母,ASCII完全可以胜任。ASCII使用数字32到 127来表示所有的英文字母,比如空格是32,字母"A"是65等等。使用7个比特就可以存储所有这样字符。那个时代的大多数计算机使用8个比特来,所以你不但可以存储全部的ASCII,而且还有一个比特可以多出来用作其他。如果你想,你可以把它用作你不可告人的目的。32以下的码字是不可打印的,它们属于控制字符,像7表示响铃,12表示打印机换纸。

所有的一切都看起来那么完美,当然前提你生在一个讲英文的国家。

关于字符集和Unicode的相关知识

因为一个字节有8个比特,而现在只用了7个,于是很多人就想到"对呀,我们可以使用128-255的码字来表示其他东西"。麻烦来了,这么多人同时出现了这样的想法,而且将之付诸实践。于是IBM-PC上多了一个叫OEM字符集的东西,它包括了一些在欧洲语言中用到的重音字符,还有一些画图的字符,比如水平线、垂直线等,水平线在右端会带一个小弯钩,垂直线会如何等等。使用这些画图字符你可以画出漂亮的框、画出光滑的线条,在老式的烘干机上的8088电脑上你依然可以看到这些字符。事实上,当PC在美国之外的地方开始销售的时候,OEM字符集就完全乱套了,所有的厂商都开始按照自己的方式使用高128个码字。比如在有些PC上,130表示é,而在另外一些在以色列出售的计算机上,它可能表示的是希伯来字母ג,所以当美国人把包含résumés这样字符的邮件发到以色列时,就为变为rגsumגs。在大多数情况下,比如俄语中,高128个码字可能用作其他更多的用途,那么你如何保证俄语文档的可靠性呢?

最终ANSI标准结束了这种混乱。在标准中,对于低128个码字大家都无异议,差不多就是ASCII了,但对于高128个码字,根据你所在地的不同,会有不同的处理方式。我们称这样相异的编码系统为码页(code pages)。举个例子,比如在以色列发布的DOS中使用的码页是862,而在希腊使用的是737。它们的低128个完全相同,但从128往上,就有了很大差别。MS-DOS的国际版有很多这样的码页,涵盖了从英语到冰岛语各种语言,甚至还有一些"多语言"码页。但是还得说,如果想让希伯来语和希腊语在同一台计算机上和平共处,基本上没有可能。除非你自己写程序,程序中的显示部分直接使用位图。因为希伯来语对高128个码字的解释与希腊语压根不同。

同时,在亚洲,更疯狂的事情正在上演。因为亚洲的字母系统中要上千个字母,8个比特无论如何也是满足不了的。一般的解决方案就是使用DBCS- "双字节字符集",即有的字母使用一个字节来表示,有的使用两个字节。所以处理字符串时,指针移动到下一个字符比较容易,但移动到上一个字符就变得非常危险了。于是s++或s--不再被鼓励使用,相应的比如Windows下的AnsiNext和AnsiPrev被用来处理这种情况。

可惜,不少人依然坚信一个字节就是一个字符,一个字符就是8个比特。当然,如果你从来都没有试着把一个字符串从一台计算机移到另一台计算机,或者你不用说除英文以外的另一种语言,那么你的坚信不会出问题。但是互联网出现让字符串在计算机间移动变得非常普遍,于是所有的混乱都爆发了。非常幸运,Unicode适时而生。

Unicode

Unicode 是一个勇敢的尝试,它试图用一个字符集涵盖这个星球上的所有书写系统。一些人误以为Unicode只是简单的使用16比特的码字,也就是说每一个字符对应 16比特,总共可以表示65536个字符。这是完全不正确的。不过这是关于Unicode的最普遍的误解,如果你也这样认为,不用感到不好意思。

事实上,Unicode使用一种与之前系统不同的思路来考虑字符,如果你不能理解这种思路,那其他的也就毫无意义了。

到现在为止,我们的做法是把一个字母映射到几个比特,这些比特可以存储在磁盘或者内存中。

A -> 0100 0001

在Unicode中,一个字母被映射到一个叫做码点(code point)的东西,这个码点可以看作一个纯粹的逻辑概念。至于码点(code point)如何在内存或磁盘中存储是另外的一个故事了。

在Unicode中,字母A可看做是一个柏拉图式的理想,仅存在于天堂之中:(我的理解是字母A就是一个抽象,世界上并不存在这样的东西,如果数学里面的0、1、2等一样)

A

这个柏拉图式的AB不同,也与a不同,但与AA相同。这个观点就是Times New Roman字体中的A与Helvetica字体中的A相同,与小写的"a"不同,这个应该不会引起太多的异议。但在一些语言中,如何辨别一个字母会有很大的争议。比如在德语中,字母 ß是看做一个完整的字母,还是看做ss的一种花式写法?如果在一个字母的形状因为它处在一个单词的末尾而略有改变,那还算是那个字母吗?阿拉人说当然算了,但希伯来人却不这么认为。但无论如何,这些问题已经被Unicode委员会的这帮聪明人给解决了,尽管这花了他们十多年的时间,尽管其中涉及多次政治味道很浓的辩论,但至少现在你不用再为这个操心了,因为它已经被解决。

每一个字母系统中的每一个柏拉图式的字母在Unicode中都被分配了一个神奇的数字,比如像U+0639。这个神奇数字就是前面提到过的码点(code point)。U+的意思就是"Unicode",后面跟的数字是十六进制的。U+0639表示的是阿拉伯字母Ain。英文字母A在Unicode中的表示是U+0041。你可以使用Windows 2000/XP自带的字符表功能或者Unicode的官方网站(www.unicode.org)来查找与字母的对应关系。

事实上Unicode可以定义的字符数并没有上限,而且现在已经超过65536了。显然,并不是任何Unicode字符都可以用2个字节来表示了。

举个例子,假设我们现在有一个字符串:

Hello

在Unicode中,对应的码点(code point)如下:

U+0048 U+0065 U+006C U+006C U+006F

瞧,仅仅是一堆码点而已,或者说数字。不过到现在为止,我们还没有说这些码点究竟是如何存储到内存或如何表示在email信息中的。

标签:Unicode

相关文章

随机推荐