一些你需要了解的基础知识,才能开始深入学习
了解字符编码如何把字节映射为字符,理解纯文本并不存在
很少有人想过,文本其实非常复杂。先从字母表说起。巴布亚新几内亚的布干维尔地区拥有世界上字母最少的语言;他们的罗托卡斯字母表仅包含12个字母:A、E、G、I、K、O、P、R、S、T、U 和 V。而另一方面,像中文、日文和韩文这样的语言则有成千上万的字符。英语当然有26个字母——如果把大小写分开算,就是52个——再加上一些标点符号,如 !@#$%&。
当你提到“文本”时,你可能想到的是“电脑屏幕上显示的字符和符号”。但计算机并不处理字符和符号;它们处理的是比特和字节。你曾在电脑屏幕上看到的每一段文本,实际上都是以某种特定的“字符编码”存储的。粗略地说,字符编码提供了一种映射关系,将你屏幕上看到的内容与计算机实际在内存或磁盘中存储的数据联系起来。存在多种不同的字符编码,有些针对特定语言(如俄语、中文或英文)进行了优化,有些则可用于多种语言。
但实际上情况更复杂。许多字符在不同编码中都存在,但每种编码可能用不同的字节序列来存储这些字符。因此,你可以把字符编码看作一种解密密钥。当有人给你一串字节——一个文件、一个网页等等——并声称这是“文本”时,你必须知道他们使用了哪种字符编码,才能将字节正确解码为字符。如果他们给错了密钥,或者根本没有提供密钥,那你只能自己去破解这个编码。几乎可以肯定你会出错,结果就是乱码。
你过去以为关于字符串的一切都是错的。
你肯定见过这样的网页,本该是撇号的地方却出现了奇怪的问号状字符。这通常意味着网页作者没有正确声明字符编码,浏览器只能猜测,最终导致预期和意外的字符混在一起。在英文中只是令人恼火;但在其他语言中,结果可能是完全无法阅读。
世界上每种主要语言都有对应的字符编码。由于每种语言不同,而历史上内存和磁盘空间都很昂贵,因此每种编码都针对特定语言进行了优化。这意味着每个编码都用相同的数字范围(0–255)来表示该语言的字符。例如,你可能熟悉 ASCII 编码,它用 0 到 127 的数字表示英文字符。(65 是大写“A”,97 是小写“a”,依此类推。)英语的字母表非常简单,因此可以用不到 128 个数字完整表达。如果你会二进制计算,那就是一个字节中的 7 位。
西欧语言如法语、西班牙语和德语比英语有更多的字母。更准确地说,它们包含带各种变音符号的字母,比如西班牙语中的 ñ。这类语言最常用的编码是 CP-1252,也称为“windows-1252”,因为它广泛用于微软 Windows 系统。CP-1252 与 ASCII 共享 0–127 范围内的字符,然后扩展到 128–255 范围,用于表示如带波浪线的 n(241)、带两点的 u(252)等字符。但它仍然是单字节编码;最大的数值 255 仍然可以放进一个字节。
接着是中文、日文和韩文这类语言,它们的字符数量太多,必须使用多字节字符集。也就是说,每个“字符”由一个 0 到 65535 的两字节数字表示。但不同的多字节编码仍然面临与不同单字节编码相同的问题:它们用相同的数字表示不同的含义。只不过数字范围更广,因为需要表示的字符更多。
在过去没有网络的世界里,这种情况还勉强说得通。“文本”是你自己输入的,偶尔打印出来而已。几乎没有真正的“纯文本”。源代码是 ASCII 格式,其他人使用文字处理软件,这些软件定义了自己的非文本格式,同时保存字符编码信息以及丰富的排版样式等。人们用和原作者相同的文字处理程序打开文档,基本上一切都能正常工作。
但现在想想电子邮件和互联网这类全球网络的兴起。大量“纯文本”在全球范围内流动,由一台电脑编写,经过第二台电脑传输,再由第三台电脑接收并显示。计算机只能识别数字,但同样的数字可能代表不同的意思。糟糕!该怎么办?于是系统必须设计成随每一份“纯文本”一起携带编码信息。记住,编码就是那个将计算机可读的数字转换为人可读字符的解密密钥。缺少密钥就意味着文本混乱、出现乱码,甚至更糟。
再想想要把多段文本存放在同一个地方的情况,比如同一个数据库表里存放你收到的所有邮件。你仍然需要为每段文本保存其字符编码信息,以便正确显示。觉得这很难吗?试试在邮件数据库中搜索吧——这意味着要实时进行多种编码之间的转换。听起来有趣吗?
再想想多语言文档的可能性,即同一文档中混合了多种语言的字符。(提示:以前尝试实现这种功能的程序通常使用转义码切换“模式”。啪的一下,进入俄语 KOI8-R 模式,241 表示 Я;啪的一下,又进入 Mac 希腊语模式,241 表示 ώ。)而且你当然还想搜索这些文档。
现在,放声大哭吧,因为你过去以为关于字符串的一切都是错的,而根本不存在什么“纯文本”。