每个程序员都必须懂的字符集和编码知识

我遗憾地发现,究竟有多少软件开发人员并没有真正走进字符集,编码,Unicode的世界。N年前,FogBUGZ的Beta测试人员想知道它是否可以处理日语的电子邮件。日语?他们有日文电子邮件吗?我不知道。当我仔细查看用于解析MIME电子邮件的商业ActiveX控件时,我们发现它在字符集方面做的完全不对,因此我们实际上不得不编写英勇的代码来撤消所做的错误转换并重做它。当我查看另一个商业库时,它的字符代码实现也完全被破坏了。我与该程序包的开发人员进行了通讯,他认为他们“对此无能为力”。像许多程序员一样,他只是希望一切都会以某种方式结束。

然而,它并不难。

在本文中,我将向您详细介绍每个工作的程序员应该知道的内容。关于“纯文本= ASCII =字符是8位”的所有内容不仅是错误的,而且是无可救药的错误,而且,如果您仍然按照这种方式进行编程,那么您就是一个不知道细菌的医生。

在开始之前,我应该警告您,如果您是了解国际化的稀有人士之一,那么您将发现我的整个讨论有些过分简化。我实际上只是想在此处设置一个最低限度,以便每个人都可以理解正在发生的事情,并可以编写希望使用除英语子集以外的任何语言的文本(不包括带有重音的单词)的代码。而且我要警告您,字符处理只是创建可在国际范围内使用的软件的一小部分。

历史角度

了解这些内容的最简单方法是按时间顺序进行。
在这里插入图片描述
早在半古老时代,当K&R发明Unix并编写C语言时,一切都非常简单。EBCDIC即将退出。唯一重要的字符是旧的,没有重音的英文字母,我们为他们提供了一个名为ASCII的代码,该代码能够使用32到127之间的数字表示每个字符。空格为32,字母“A”为65,依此类推。这可以方便地以7位存储。当时的大多数计算机都使用8位字节,因此,不仅可以存储所有可能的ASCII字符,而且还有很多余地。低于32的代码被称为不可打印的,它们用于控制字符,例如7会使计算机发出哔哔声,而12则导致当前纸张飞出打印机,然后再送入新的一页。

假设您说英语,一切都很好。
在这里插入图片描述
因为1字节最多可以8位,所以很多人开始想:“天哪,我们可以出于自己的目的使用代码128-255。”麻烦的是,很多人同时有这个想法,他们对从128到255的空间应该有什么也有自己的想法。IBM-PC的字符集后来被称为OEM字符集,它提供了一些用于欧洲语言的重音符号和一堆绘图字符……水平条,垂直条,水平线,垂线等,您可以使用这些线条图字符来制作漂亮的盒子和屏幕上的线条。实际上,只要人们开始在美国以外的地方购买PC,就会构想出各种不同的OEM字符集,它们都将另外128个字符用于自己的目的。例如,在某些PC上,字符代码130将显示为é,但是在以色列出售的计算机上,该字符是希伯来字母Gimel(ג),因此,当美国人将简历发送给以色列时,它们将以rגsumג的形式到达。在许多情况下(例如俄语),对于使用大写128字符的处理方式有很多不同的想法,因此您甚至无法可靠地交换俄语文档。

最终,这个免费的OEM字符集成为了ANSI标准。在ANSI标准中,大家都同意在128以下与ASCII相同,但是根据地区的不同,有很多不同的方法可以处理128以上的字符。这些不同的系统称为code pages。例如,在以色列,DOS使用的代码页为862,而希腊人使用的是737。128以下的字符相同,而128以上则不同,各种有趣的字母都在那里。

到了亚洲,更加令人抓狂的是亚洲语言具有数千个字,它们无法适应8位。 这通常通过称为DBCS的凌乱系统解决,该系统是“双字节字符集”,其中一些字符存储在一个字节中,而另一些则存储在两个字节中。在字符串中向前移动很容易,但是几乎不可能后退。程序员被建议不要使用s++和s–来回移动,而是调用诸如Windows的AnsiNext和AnsiPrev之类的函数来处理这个混乱的情况。

但是,大多数人还是假装一个字节是一个字符,一个字符是8位,并且只要您从未将字符串从一台计算机移动到另一台计算机,或者说一种以上的语言,它就总是可以工作的。 但是,当然,一旦Internet出现,将字符串从一台计算机移到另一台计算机就变得很普遍了,整个混乱局面逐渐恶化。 幸运的是,Unicode被发明了。

Unicode

Unicode是一个勇敢的工作,旨在创建一个字符集,其中包括地球上所有合理的书写系统以及一些像Klingon这样的虚构语言。有人误以为Unicode只是一个16位代码,其中每个字符占用16位,因此有65,536个可能的字符。实际上,这是不正确的。这是关于Unicode的最普遍的传言,因此,如果您这样认为,请不要感到难过。

实际上,Unicode具有不同的字符思考方式,您必须了解Unicode的思考方式,否则没有任何意义。
到目前为止,我们假设一个字母映射到一些可以存储在磁盘或内存中的位:

A -> 0100 0001

在Unicode中,字母映射到称为码点(code point)的位置,这仍然只是一个理论上的概念。该代码点如何在内存或磁盘上表示是一个完整的故事。
在Unicode中,字母A是柏拉图式的理想选择。它只是漂浮在天堂:A

这个柏拉图式的 A 与 B 不同,并且与 a 不同,但与 AA 相同。这种想法是,Times New Roman字体中的A与Helvetica字体中的A相同,但与小些的 “a” 不同。这似乎并没有什么问题,但是在某些语言中,到底字母是什么会引起争议。 德语字母 ß 是字母还是仅仅是写 ss 的一种奇特方式?如果字母的形状在单词的末尾发生变化,那是另一个字母吗?希伯来语说是,阿拉伯语说不是。无论如何,在过去的多年里,Unicode协会的聪明人一直在弄清楚这一点,伴随着大量的政治辩论,您不必担心。他们已经弄清楚了。

Unicode协会为每种语言中的每个柏拉图字母分配了一个魔幻数字,其写法如下:U+0639。 这个神奇的数字称为码点。U+表示“ Unicode”,数字为十六进制。U+0639是阿拉伯字母Ain。英文字母A为U+0041。您可以访问 Unicode 网站找到它们。

Unicode可以定义的字母数量没有实际限制,实际上,它们已经超过65,536个,因此并不是每个unicode字母都可以真正压缩为两个字节。
OK,假设我们有一个字符串: Hello

在Unicode中,它对应于以下五个代码点:
U+0048 U+0065 U+006C U+006C U+006F

只是一堆代码点。关于如何将其存储在内存中或以显示,我们还没有说过。

编码

这就是编码的来源。

Unicode编码最早的思想是关于两个字节的神话,嘿,让我们将这些数字分别存储在两个字节中。所以 Hello 成为

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

对吗?等等!也可能是:

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

好吧,从技术上讲,是的,我确实相信它可以,而且,事实上,早期的实现者希望能够以高位优先或低位优先模式存储Unicode代码点,无论哪种更快。而且,已经有两种存储Unicode的方法。因此人们被迫提出一个奇怪的约定,即在每个Unicode字符串的开头存储一个FE FF。这被称为Unicode Byte Order Mark,如果您要交换高字节和低字节,它将看起来像FF FE,并且读取字符串的人将知道他们必须交换其他所有字节。 然而并非每个Unicode字符串开头都有Unicode Byte Order Mark。

一段时间以来,这似乎已经足够好了,但是程序员一直在抱怨。 “看看那些零!”他们说,因为他们是美国人,所以他们在看英文文本,而该文本很少使用U+00FF以上的代码点。而且,已经有了使用各种ANSI和DBCS字符集的所有这些笨拙的文档,谁来将它们全部转换呢?仅出于这个原因,大多数人决定在几年内忽略Unicode,与此同时情况变得更糟。

因此出色的 UTF-8 诞生了。UTF-8是另一个使用8位字节将Unicode码点的字符串(那些神奇的U +数字)存储在内存中的系统。在UTF-8中,从0-127的每个码点都存储在一个字节中。实际上,只有码点128和更高的码点才使用2、3(最多6个)个字节存储。
在这里插入图片描述
这样做的好处是,英语文本在UTF-8中的外观与在ASCII中的外观完全相同,因此美国人甚至不会发现任何错误。只有世界其他地方才可以跳过障碍。具体来说,Hello,即U+0048 U+0065 U+006C U+006C U+006F,将被存储为48 65 6C 6C 6F,这与存储在ASCII和ANSI中以及地球上每个OEM字符集相同。现在,如果您大胆使用重音字母,希腊字母或克林贡字母,则必须使用多个字节来存储单个代码点,但是美国人永远不会注意到。(UTF-8还具有不错的属性,即使用单个0字节作为终止符的旧字符串处理代码不会截断字符串)。

到目前为止,我已经介绍了三种Unicode编码方式。传统的“两字节存储”方法称为UCS-2(因为它有两个字节)或UTF-16(因为它有16位),您仍然必须弄清楚它是否是高位UCS- 2或低位UCS-2。而且还有流行的UTF-8标准,如果您只阅读英文文本,完全不知道除了ASCII以外的任何其他功能,它也具有良好的性能。

实际上,还有许多其他编码Unicode的方式。有一种叫做UTF-7的东西,它与UTF-8很像; UCS-4具有将每个代码点存储在4个字节中的功能,它具有很好的属性,即每个单个代码点都可以存储在相同数量的字节中,但是,太可惜了,它太浪费内存了。

实际上,您现在已经以Unicode码点表示的柏拉图式字母的方式思考问题了,那些unicode码点也可以采用任何老式的编码方案进行编码!例如,您可以使用ASCII编码Hello(U+0048 U+0065 U+006C U+006C U+006F)的Unicode字符串,或者使用旧的OEM希腊编码,希伯来ANSI编码或数百种编码中的任何一种,只有一个问题:有些字母可能不会出现!如果您尝试使用的编码形式中没有与Unicode代码点等效的形式,通常会出现一个问号: -> �

有数百种传统编码,它们只能正确存储一些代码点,而将所有其他代码点更改为问号。某些流行的英语文本编码是Windows-1252(西欧语言的Windows 9x标准)和ISO-8859-1,又名Latin-1(对任何西欧语言也有用)。但是,尝试以这些编码存储俄语或希伯来语字母,则会出现很多问号。 UTF 7、8、16和32都具有能够正确存储任何代码点的优点。

关于编码的最重要的事实

如果您完全忘记了我刚才解释的所有内容,请记住一个极其重要的事实:不知道编码的字符串就没有意义。您不能再把头埋在沙子里,并假装“纯文本”是ASCII。

没有 Plain Text 这样的东西。

如果您在内存,文件或电子邮件中有字符串,则必须知道字符串的编码,否则无法解释它或将其正确显示给用户。

几乎每个愚蠢的“我的网站是乱码”或“当我使用重音符号时她都无法阅读我的电子邮件”的问题归结为一个天真的程序员,他不理解一个简单的事实,即如果您不告诉我某个特定的字符串使用UTF-8或ASCII或ISO 8859-1(拉丁语1)或Windows 1252(西欧语)编码,您根本无法正确显示它,甚至无法弄清楚它的结尾。有上百种编码,并且在代码点127之上,所有猜测均无效。

我们如何保存有关字符串使用什么编码的信息?好吧,有标准的方法可以做到这一点。对于电子邮件,应该在表格的标题中包含一个字符串

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

对于HTML,最初的想法是Web服务器将Content-Type作为在HTML页之前发送的response headers之一。

这会引起问题。假设您有一台大型Web服务器,其中包含许多站点,数百页的页面由许多人以多种不同的语言提供。网络服务器本身并不真正知道每个文件的编码方式,因此它无法发送Content-Type头。

如果可以使用某种特殊标记将HTML文件的Content-Type放到HTML文件本身中,将会很方便。当然,这使纯粹主义者发疯了……您如何才能读取HTML文件,如果您不知道它的编码是什么?!幸运的是,几乎所有常用的编码都使用32到127之间的字符来做同样的事情,因此,您始终可以在HTML页面上做到这一点而无需开始使用有趣的字母:

<html>
<head>
<meta http-equiv=“Content-Type” content=“text/html; charset=utf-8”>

但是,该meta标记确实必须是**<html>**部分中的第一件事,因为一旦浏览器看到此标记,它将立即停止解析页面,并使用您指定的编码重新解析整个页面。

如果浏览器在http标头或meta标记中找不到任何Content-Type,该怎么办?Internet Explorer实际上做了一些非常有趣的事情:它尝试根据各种语言以各种语言的典型编码出现在典型文本中的各个字节的出现频率来猜测。因为各种旧的8位代码页都倾向于将其本国字母放在128到255之间的不同范围内,并且由于每种人类语言都有不同的字母用法特征。确实很奇怪,它似乎经常能工作,以至于那些天真烂漫的网页编写者从未知道他们需要Content-Type标头在网络浏览器中浏览他们的页面,而且看起来还可以,直到有一天,他们写的东西没有并不完全符合他们母语的字母频率分布,Internet Explorer决定使用朝鲜语并以此显示。

本文篇幅相当长,我可能无法涵盖有关字符编码和Unicode的所有知识,但是我希望,如果您已经读了那么多文字,那么您将了解到足够多的知识。

本文翻译并节选自:
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值