软件开发者必须绝对、完全了解的关于Unicode和字符集的基础知识——别找借口
每个软件开发人员绝对、肯定必须了解的关于 Unicode 和字符集的最低要求(没有借口!)
相关信息
本文翻译自 https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
以下是文章《The absolute minimum every software developer absolutely, positively must know about Unicode and character sets, no excuses》的完整中文翻译:
软件开发者必须绝对、完全了解的关于Unicode和字符集的基础知识——别找借口
你是否曾经困惑于那个神秘的 Content-Type 标签?就是你应该放在 HTML 里但又不太确定应该写什么的那个?
你有没有收到过朋友从保加利亚发来的邮件,主题却显示成了“???? ?????? ??? ????”,让你摸不着头脑?
我一直很震惊的是,竟然有这么多软件开发者对字符集、编码、Unicode 这些东西并不完全了解。几年前,FogBUGZ 的一位测试人员问我们软件是否支持处理日文邮件。日文邮件?他们居然有日文邮件?我当时完全没意识到。当我仔细检查我们用来解析 MIME 邮件的商用 ActiveX 控件时,发现它对字符集的处理完全错乱,我们不得不写大量复杂代码去纠正控件错误转换的结果。后来我查看另一款商用库,结果发现字符编码实现也完全有问题。我和那个库的开发者交流过,他似乎认为“他们无能为力”……像很多程序员一样,他只希望问题能自己消失。
但问题不会消失。当我发现流行的Web开发工具 PHP 几乎完全无视字符编码问题,愚蠢地用 8 位表示字符,导致开发国际化网络应用几乎不可能时,我心想:够了!
所以我要宣布:如果你是 2003 年还在写程序的开发者,而你连字符、字符集、编码、Unicode 的基本知识都不知道,我告诉你,我一定会抓住你,送你去潜艇上剥洋葱六个月。说真的。
还有一件事:
这并不难。
本文将告诉你,所有在职程序员必须知道的内容。关于“纯文本=ascii=字符是8位”这种说法,不但错,而且错得彻底。如果你还这么编程,你就和那个不相信细菌会传染的医生没多大区别。请你务必读完这篇文章,别写下一行代码。
我得提醒你,如果你是那种少数真正懂得国际化的人,可能会觉得我说得太简单了。我只是想立一个基本门槛,让大家都能明白发生了什么,并写出能处理除无重音英语以外的任何语言的文本的代码。我还得声明,字符处理只是做国际化软件的极小一部分,但今天我就讲字符集。
历史概述
理解这些,最简单的方式是按时间顺序说。
你可能以为我要讲非常古老的字符集,比如 EBCDIC,对吧?不会。EBCDIC 对你基本无关紧要,不必谈那么远。
在半旧时代,Unix 刚发明,K&R 写《C 语言程序设计》,一切都很简单。EBCDIC 正在走向没落。唯一重要的字符是纯英文无重音的字母,我们有一个叫 ASCII 的编码,它用数字 32 到 127 表示每个字符。空格是 32,字母 A 是 65,等等。ASCII 可以存储在 7 位里。大多数当时的计算机用 8 位字节,不但能存储所有 ASCII 字符,还多出一位,你如果有心机,还可以用这位藏点小机关:WordStar 就用最高位标记单词最后一个字母,把 WordStar 整成只能处理英文文本……32 以下的码被称为不可打印字符,用作控制字符,比如 7(让计算机响铃)和 12(让打印机换纸)。
一切都挺好,只要你是讲英语的人。
由于 8 位字节有多余一位,很多人都想,“128 到 255 的编码我们也能用”,但问题是……大家都同时这么想,而且每个人都自己定规则:IBM-PC 有一个 OEM 字符集,提供欧洲语言的重音字符,和很多线条绘制字符,可以用来在屏幕上画表格线,这玩意你到干洗店挂的 8088 电脑上都能看到。人们买 PC 时,世界各地都发明了自己的 OEM 字符集,全部用最高 128 个字符位置搞事情……比如同一个编码 130,欧洲 PC 显示 é,而以色列电脑显示希伯来字母 Gimel(ג),以色列收到美国人简历时变成了“rsums”。俄语因为使用多种编码,彼此之间文档根本不能互换。
最后,这些 OEM 混战被 ANSI 标准规范了。ANSI 规定 0-127 跟 ASCII 基本相同,128 以上看地区用不同的代码页,比如以色列用 862,希腊用 737,英文和冰岛语也有自己的代码页,甚至有多语种代码页能同时处理世界语和加利西亚语。但是希伯来语和希腊语用的代码页互不兼容——除非你写自己的位图程序。亚洲就更疯狂,亚洲字母有成千上万字符,根本放不进 8 位,这就出现了 DBCS(双字节字符集),有些字符用 1 字节,有些用 2 字节,向前遍历简单,向后遍历几乎不可能。程序员被建议别用 s++ s-- 操作 string,而调用 Windows 的 AnsiNext 和 AnsiPrev。
但大多数人仍假装字节等于字符,字符就是 8 位,只要不跨电脑环境或多语环境,还能将就着用。结果互联网的出现让字符串跨电脑、跨语言传输成为常态,这个假装破碎了。幸好 Unicode 出现了。
Unicode
Unicode 是一次勇敢的尝试,想创建一个涵盖全世界所有合理文字,还有克林贡语这种虚构文字的单一字符集。有些人误以为 Unicode 就是 16 位编码,每个字符占 16 位,所以有 65,536 个字符可能。
这其实是不对的。 这是关于 Unicode 最常见的误解之一——如果你这么想,不必羞愧。
Unicode 把字符定义为码点(code point),这只是一个理论概念。码点如何存储在内存或磁盘上,是另一回事。
Unicode 中,字母 A 是纯粹的理念:
A
这个理念中的 A 和 B 不同,和小写 a 不同,但和另一个 A 相同。字体中的 A(Times New Roman 和 Helvetica)是同一个字符,但和小写 a 不同。在有些语言中,字母本身是什么也有争议,比如德语 ß 是一个字母还是 ss 的写法?单词结尾处字母形状变化是不是新字母?希伯来语说是,阿拉伯语说不。Unicode 委员会耗费十年政治斗争搞定了这些问题,你不用操心。每个理想字母都有一个魔法数字,叫做码点,用 U+ 加十六进制数表示。
比如阿拉伯字母 Ain 是 U+0639
,英文 A 是 U+0041
。你可以用 Windows 的 charmap 工具或者 Unicode 官网查。
Unicode 理论上没有字符数量上限,他们已经超了 65,536 个,不是所有 Unicode 字符能塞进两个字节,这个“16 位”说法是谬论。
比如字符串“Hello”对应码点序列:
U+0048 U+0065 U+006C U+006C U+006F
这些都是数字,暂时不谈如何存储。
编码(Encodings)
Unicode 本身是码点的抽象概念,编码决定这些码点如何存储。
最早的 Unicode 编码思想是:每个码点用两个字节存储,于是 "Hello" 变成:
00 48 00 65 00 6C 00 6C 00 6F
但这样到底是大端还是小端?有两种存储不同字节序的办法。于是大家发明了字节顺序标记(BOM),在字符串开头放 FE FF
或 FF FE
标记存储序,方便读者判断。
然而程序员抱怨:“好多零!” 因为英文主要用 U+00FF 以下的码点,导致存储浪费。
后来加州的程序员发明了 UTF-8,使用 8 位字节来存储 Unicode 码点。UTF-8 设计巧妙,码点 0-127 用单字节表示,和 ASCII 一模一样,从 128 以上才用多字节编码。
因此英语文本用 UTF-8 存储和 ASCII 完全兼容。比如“Hello”的 UTF-8 是:
48 65 6C 6C 6F
和 ASCII 一模一样。
如果用的是变音字母或希腊字母等,则需要多字节表示。
UTF-8 还有一个好处是字符串处理程序如果用单字节零作为字符串结束符,不会误截断字符串。
其他 Unicode 编码
除了 UTF-8,还有 UCS-2(传统两字节编码)、UTF-16(16位编码)、UTF-7(保证高位总是0,便于通过只能传7位的系统)、UCS-4(4字节固定编码)等等。
其实你甚至可以用 ASCII、OEM、ISO-8859-1 等老编码表示 Unicode 字符串,只要字符能被支持,但常会出现“?”或者方框代替无法显示的字符。
关于编码的最重要事实
如果你忘了本文所有内容,请至少记住一点:
没有编码信息,你无法正确解读字符串。
不要再假装“纯文本就是ASCII”了。
结语
无论字符串是在内存、文件还是邮件中,都必须知道它的编码,否则无法正确显示和处理文本。
这就是程序员必须了解的 Unicode 和编码的基础知识——没有借口,不容忽视。
希望这篇翻译能够帮助你更好理解Unicode和字符编码的重要性!
Answer from Perplexity: pplx.ai/share