软件开发者必须要知道Unicode和字符集,这是最起码的要求(别找借口)

软件开发者必须要知道Unicode和字符集,这是最起码的要求(别找借口)

有没有想过那个神秘的content-type标签?你知道的,就是那个你应该把它放入HTML中,并且你绝不会马上就知道 它应该是什么。

你是否曾经收到过来自保加利亚(Bulgaria)朋友的来信,信内主题行是“???? ?????? ??? ????”?

我很沮丧地发现许多软件开发人员完全没有跟上字符集奥秘世界发展的速度,编码,Unicode,所有的东西。几年前,FogBugz(一个贝塔测试仪)思考着,是否可以处理来自于日本的信件。日本?他们有来自日本的信件?我不知道。当我仔细观察用来解析MIME邮件消息的商业ActiveX控件时,我发现它对字符集做了完全错误的事情,因此,我们必须编写heroic代码去撤销它所做的错误转换并正确地重做它。当我查看了另一个商业库,它也是这样的,有一个残损的代码实现。我与包实现者通信,他认为他们对此无能为力,和许多程序员一样,他仅仅期望着它能被某种方式摧毁掉。

但它不会。当我发现流行的web开发工具PHP几乎完全忽略字符编码问题,轻松使用8位字符,这使得它不可能开发出好的国际化web应用,我认为,够了就是够了。

因此我有一个声明要发表:如果你是一名在2003年工作的程序员并且你不知道字符,字符集,编码和Unicode的基础知识,如果我逮到你,我将通过让你在潜水艇里削洋葱6个月作为惩罚。我发誓,我会的。

还有一件事:

这并不难

在本文中,我将吧每个工作程序员应该知道的确切内容告诉你。所有关于“纯文本=ASCII=字符是8位”的事情不只是错误的,而且是无可救药(绝望)的错误,如果你仍旧以那种方式编码,你不会比一名不相信细菌的医学博士要好。请不要再编写一行代码,直到你完成阅读本文

再开始前,我应该以你是知道关于国际化的其中一人警醒你,你将会发现我的整个讨论有些过于简单。我确实只是在这里尝试着设置一个最小栏,以便每个人都能知道发生了什么,并且能够有希望编写出包含任何语言的代码,而且不仅仅是不包含英文重音的英文子集。我应该警醒你字符处理仅仅是创建国际化软件所需的一小部分。但我每次仅能书写一件事情,因此,今日轮到字符集了。

历史的视角

理解这些内容最简单的方式就是按时间次序。

你可能认为我会在这里讨论像EBCDIC这样的非常古老的字符集。我不会的。EBCDIC与你的生活无关。我们不必没必要向后追溯那么久远。

追溯到semi-olden时间,当Unix被创造后并且K&R正在写C语言,所有东西都很简单。EBCDIC正退出。唯一有关系的旧的无重音字符就是英文字母,对于这些字母,我们给它一个代号,叫ASCII,它允许它们使用32到127之间的每个数字来表示每个字符。 空格是32,大写字母“A”是65,等等。这样可以很方便的保存在7位(bit)里。现代电脑大部分都是使用8位字节,因此你不仅可以存储每一个ASCII字符,你还有一整个bit闲置出来,如果思维灵活,你可以用这个空闲的bit来存一些别有目的的字符:WordStar(文字处理软件)中的暗淡的灯泡高位打开是为了表明单词的最后一个字母,WordStar最不好的地方就是仅支持英文文本。少于32位的代码称为不可打印代码,它是用于诅咒的。开玩笑的。他们是用作控制字符,例如7会让你的机器beep,12会导致当前页面的纸张从打印机中送出,新的一页被送入打印机。

一切都很好,假设你的母语是英语。因为我们用8位去存储字符,很多人就会都想到:“嘿嘿,我们可以用128-255位置来存储我们自己的符号。

软件开发者必须要知道Unicode和字符集,这是最起码的要求(别找借口)_第1张图片

麻烦的是,太多人在同一时间有这个想法了,对于怎么处置128-255的位置,他们有自己的想法。IBM的电脑提供了一堆叫OEM字符的东西,用来提供欧洲的重音字符和一堆线条人物。横的、竖的、垂直右边有个小铃铛的等等。你可以用这些线条字符在屏幕上去堆一个精美的盒子出来,实际上你依然可以在使用8088电脑的干衣机上看到它们的身影。实际上,随着美国以外的人们开始买属于自己的电脑以后,不同的128个字母都被赋予了他们各人的目的。例如,在一些电脑上面字符编码130会显示é,但是在以色列的电脑上它就会显示 ג。所以当美国人想发送他们的résumés 到以色列的话,就会变成了rגsumגs。在很多案例中,例如俄罗斯,他们对这128位之后的字符编码的使用有着大量不同的想法,所以你很难去可靠的交换俄罗斯的文件。

最后,OEM决定编纂ANSI标准。在ANSI标准里面,所有人都同意如何使用128位以下的位,这几乎与ASCII相同,但是从128位开始到最后一个编码位的使用就大有不同。这些不同的系统被称为编码页(Code Page)。例如在以色列的DOS里面使用的编码页被称为862,希腊用户用的称为737。他们使用的128位以下都是相同的,但是从128位以后开始就充斥了各种有趣的字母了。MS-DOS国家版本里面有几十个这些编码页,可以处理英语到冰岛语,甚至还可以在同一台电脑里面处理世界语和加利亚语!屌爆!但是,希伯来语和希腊语在同一台计算机上是完全不可能的,除非你编写自己的自定义程序用来显示所有使用位图的图形,因为希伯来语和希腊语需要不同的编码页与高数字。

与此同时,亚洲有着成千上百个字符,这些远远不能放进8位里面。这一样会通过称为DBCS的五花八门的系统来解决,“双字节字符集”里面存着字符一些占用了一个字节,一些占用了两个字节。这样在一个字符串中很容易可以向前移,但是很难向后移。程序员最好不要使用s++和s-去向前或者向后移字符串,鼓励使用例如window里面AnsiNext和AnsiPrev的方法来避免混乱。

但是,只要从来不把一个字符串移动到另一台电脑的话,或者不讲另一种语言的话,把一个字节当作一个字符或者把一个字符当作是八位是可行的。但当然,随着互联网发展,字符串从一台电脑发送到另一台电脑变得相当普遍。就在这个时候,Unicode出现了。

Unicode

Unicode致力于创造一个可以适应所有地球上操作系统的单一字符集。有些人误解Unicode只是一个简单的16位编码,每个字符占用16位所以只能保存65536个字符。这!一点!都不对!这是一个属于Unicode的神话,你不懂别bb。

实际上,关于字符,Unicode有自己的一套独到的思考方式,如果你不能了解它的想法,你会感受不到它的好。

现在,我们来假设一个字母映射到一些位中,你可以把它存在硬盘或者内存中。

A -> 0100 0001

在Unicode中,一个字母映射会被映射到一个称为 编码点 [0]的东西上,当然它只是一个概念。如何将 编码点 在硬盘或者内存中表现出来才是完整的故事。

在Unicode中,字母A只是一个柏拉图式的念想,只是漂浮在天堂上:

A

这个柏拉图式的A跟B不一样,跟a也不一样,但是跟A 和 AA 都一样。不同字体之间的字符A 都是代表同一个A,但是不同于小写 “a”,看起来无可非议,但是在一些语言里面弄清楚一个字母是有很多争议的。德国的字母 ß 是真实存在的字母还是只是一种特殊的写作方式?如果一个字母在单词里面最后一笔发生了变化,这是一个新的字母吗?Hebrew说是的,Arabic说不是。无论如何,在Unicode联盟里面的那堆聪明人经过大量的讨论辩论,这些问题已经解决了。

每个柏拉图式的字母会由Unicode联盟通过每个字母表来赋予一个魔术代码给它们,它是这个样子的:U+0639 。 这个魔术代码就是 编码点 。 编码点里面的 U+ 代表 “Unicode” ,后面的数字是十六进制数。 U+0639 是阿拉伯的字母 Ain。 英语的字母A则是 U+0041。你可以通过 Windows 2000/xp 里面的 charmap 来查询它们,或者登陆Unicode官网查询。

Unicode可以定义的字母数量并没有真正的限制,实际上它们超过了65,536,所以不是每个unicode字母都可以被压缩成两个字节,这只是一个神话。

好的,我们现在来个例子:

Hello

在Unicode里面,对应的五个编码点是:

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

反正就是一堆编码点数字什么的了。谈到这里,我们还没有提到如何把这些编码保存在内存里面和如何在一封电子邮件里面重现。

Encodings (编码)

是时候让 编码 出场啦。 Unicode编码最早的想法就是来源于那个两个字节的神话,只需要将这些数字分别存在两个字节里。就变成这样:

00 48 00 65 00 6C 00 6C 00 6F

是吧?好像不太快吧?不可以是这样吗?

48 00 65 00 6C 00 6C 00 6F 00 

在技术上,我相信可以做到的。事实上,每个实现者都希望以高地址和低地址的模式【译者注:这里提到的可以去看一下字节对齐更好理解】来存储他们的Unicode编码点。这样的话无论是任何CPU来处理,速度都是相当快的。所以人们不得不提出在每个Unicode字符串的开头存储一个FE FF的奇怪约定;这个被称为 Unicode Byte Order Mark(BOM),如果你交换了你的高低字节,那么它将会看起来是 FF FE 这样,那么其他人读到你的字符串的时候就可以知道需要把字节交换回来了。 当然了,不是每个Unicode 字符串开头都会有BOM标记的。 image

这看起来已经相当好了,当时有些程序员会抱怨。“看看那些 0 !”,他们说。因为美国人很少会用高于 U+00FF 的编码点。【译者注:然后这里一段都是讽刺德州人和加州人。。没什么营养,略去。】大意就是这些用惯了ANSI和DBCS字符集的人懒得转换使用Unicode,并且固定长度的Unicode会导致一些字符会保存大量的0字节,因此Unicode被忽略了很多年。

因此,辉煌的UTF-8被提出了。 UTF-8是使用另一个系统来存储你的Unicode编码点字符串,那些模式U+数字在内存里使用8位字节,而在UTF-8里面,每个0-127的编码点都被存在了一个独立的字节里。只有当编码点大于等于128的时候才会只用第二第三个字节,实际上,最高可以允许使用六个字节。

image

这种做法使得UTF-8看起来就跟ASCII一样整洁,因此美国佬没有发现出区别。 只有世界上其他的人才能跳出思维圈圈。具体来说,Hello, 编码点串为 U+0048 U+0065 U+006C U+006C U+006F,会被保存为48 65 6C 6C 6F。看啊哇塞!这跟保存在ASCII或者地球上其他的OEM字符集都一样啊!现在,你可以大胆地使用重音字母或者希腊字母或者克林贡字母,即使你不得不使用几个字节来保存一个编码点,但是美国佬们永远都不会注意到。(UTF-8也有一个不错的属性,那个想要使用单个0字节作为空终止符的无效旧字符串处理编码不会被截断)。

到目前为止,我已经讲述了三种Unicode编码。传统的保存在两个字节的方法被称为UCS-2(因为它有两个字节)或者UTF-16 (因为它有16位),而且你依然需要区分这是高位UCS-2还是低位UCS-2。还有很受欢迎的UTF-8。

实际上有一些其他的Unicode编码方法。 有一个叫UTF-7,非常像UTF-8,但是它保证高位总是为零,因此如果你要通过某种恶意的警察邮件系统来传递Unicode,UTF-7是足够的了。还以一种UCS-4,会把每个编码点保存到4个字节里面,优点是每个编码点都能草存到相同大小的字节里面,但是,节俭的德州人会很在意这些浪费的。

事实上,你正在思考使用Unicode编码点来表示柏拉图式字母,那些Unicode编码点也可以用任何老式的编码方案进行编码!举个例子,你可以对Unicode字符串Hello (U+0048 U+0065 U+006C U+006C U+006F) 进行ASCII编码,或者古老的OEM希腊语编码,或者Hebrew ANSI 编码,或者任何其他有上百年历史的编码方式。你会发现,都会是相同的结果:一些字母不会被显示出来!它会显示:? 或者�。

几百种传统的编码方法都只能正确的保存部分编码点,其他的编码点都会显示问号。一些流行的英文文本编码是Windows-1252(西欧语言的Windows 9x标准)和ISO-8859-1(也称为拉丁语1)(也适用于任何西欧语言)在尝试保存俄文或者Hebrew字母的时候,都会得到一大堆问号。UTF 7,8,16和32则再这方面都有着很好的表现。

关于编码的一个重要的事实

如果你已经忘光了我刚刚解释的一切,请记住一个事实。只有字符串而不知道它使用的编码,这是毫无意义的。你不可以再把头埋在洞里,假装这个文本使用的是ASCII。

没有没有这样的事情作为纯文本

如果你有一个字符串在内存,在文件或者在电子邮件信息中,你必须知道它使用的编码否则你不能正确地读到它。

“我的网站看起来乱七八糟”或者“她不能看懂我的电子邮件当我说了重音拼音”等等这些愚蠢的问题都归咎于天真的程序员,由于他根本不知道你的信息所使用的编码。

我们能不能找到有用的信息来显示字符串使用的编码方式呢?答案是有的,我们通过标准的方法来达到这个目的。对于电子邮件信息,你应该在表单的头部有一个字符串。

Content-Type: text/plain; charset=”UTF-8″

对于网页来说,最初的想法是网站服务器会返回一个类似 Content-Type 的HTTP头部信息以及网页本身 —— 不是HTML本身,作为在HTML页面发送之前的响应头之一。

但这会引起问题。假设你有很大的WEB服务器,拥有大量的页面和成百上千的页面是由不同语言的人提供的,那么所有使用的编码将会由Microsoft FrontPage选择认为适合的编码方法来生成。Web服务器本身不会真正知道每个文件的编码方式,因此无法发送Content-Type头文件。

如果你可以使用某种特殊标签将HTML文件的Content-Type正确放置在HTML文件本身,那将会很方便。当然,这会让纯粹主义者疯狂…你怎么能通过读HTML文件来确定什么编码?幸运的是,几乎每个常用的编码间32到127之间的字符都相同,所以你可以随时在HTML页面上得到这个,而不用担心使用“有趣”的字母:










要记住这个meta标签必须是块里面非常靠前定义的标签,因为当浏览器看到这个标签的时候就会停止页面的解析并且开始使用你指定的编码重新开始解析整个页面。

当浏览器找不到定义 Content-Type 的meta 标签而且http头也没有定义的时候怎么办?IE浏览器通常会做一个有趣的事情:它会尝试去猜测,基于各种语言的典型编码中的各种字节出现在典型文本中的频率,来决定使用什么语言和编码【译者注:IE大哥别乱猜啊。。】。因为各种旧的8位编码页往往将其国家字母放在128到255之间的不同范围内,并且因为每种人类语言都有不同的字母使用特征直方图,这实际上有一个可行的机会。这真的很奇怪,但这看起来的确可行,直到有一天他们写了一些完全不能根据字母频率分布来确认语言的东西出来,IE判定它是韩语并显示出来,证明这不是很好的方法。

这篇文章变得很长,我不可能涵盖所有关于字符编码和Unicode的知识,但是我希望如果你已经读到这里的话,尝试去更多的地方去了解Unicode,会让你更获益良多。

参考

  • 阮一峰-字符编码笔记:ASCII,Unicode 和 UTF-8
  • The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets
  • java字符编码原理总结
  • 转载于:https://my.oschina.net/u/203607/blog/1489375

你可能感兴趣的:(#,JAVA,编码,unicode,content-type,utf-8,ASCII)