原文见 what is a character encoding

要理解字符编码,那我们就首先需要知道计算机是如何存储字符数据的。我们希望当我们按下键盘上的某一个键时,电脑会在某个地方记录下一个小符号。但这只是做白日梦。

我想我们大多数人都知道在计算机内部,任何东西都会变成一串0和1的组合。这就意味着一个”a”会存储为一些数字。实际上,我们可以使用Ruby1.8看到这些数字:

$ ruby -ve 'p ?a'
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.4.0]
97

?a 这种表示方法给我们返回了单个字符,而不是一个字符串。在Ruby1.8中,他返回字符的编码后的码。你也可以利用下标从字符串取得单个字符的方式来获得这个码:

$ ruby -ve 'p "a"[0]'
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.4.0]
97

Ruby 的核心开发团队认为字符串的这种行为是令人困惑的,所以在 Ruby1.9 中修正了。它们现在会返回包含单个字符的字符串。在 Ruby1.9 中,你可以使用 getbyte() 看到编码后的码:

$ ruby_dev -ve 'p "a".getbyte(0)'
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
97

但是,这只是告诉我们如何获得这个数字,它没有告诉我们这个数字代表什么。当我们需要把一些字符转化为数字的时候,我们构造了一张表来记录数字和字符之间的对应关系。这张对应关系表就是就叫做 US-ASCII 或者 ASCII 。

现在 ASCII 遍布在英式键盘的各个地方:大小写字母,数字和其他通用符号。 ASCII 对照表的 128 个字符中其它字符用来表示一些控制符号。

生活是多么美好,是吗?哦,不。

这样可以导出了两个事实:

  1. 仅仅这些字符并不能表示整个世界

  2. 在每个字节中我们还有更多的空间,因为 ASCII 只使用了一个字节的 8 位中的 7 位(那就意味着你还可以表示128个字符)。

哦~~,我们还有一些 128 个字符的空间可以使用,并且我们也需要更多地字符。多意外啊!正由于每个人都对如何使用这 128 个字节有自己的想法,并且他们也按照自己的方式使用它。于是字符编码就诞生了。

正因为这额外的 128 个字符会随着使用不同的编码方案而含义不同,所以我们说数据是按照这种编码方案编码的。你需要知道那种编码能正确的读取数据。

一个特殊的例子是,编码方式 ISO-8859-1 (也叫做 Latin-1 )是一些操作系统、程序和编程语言中的默认方式。它主要根据一些欧洲国家的特殊字符来扩展另外 128 个字符。

如果仅仅是这额外的 128 个字符,那问题也不是非常复杂。不幸的是,还有更令人苦恼的事:有些语言甚至用 256 个字符也不能完全表示。因为 256 是一个字节所能表示字符的极限,所以这些语言需要多字节编码,也就是说需要多个字节来表示一个符号。

多字节编码处理起来非常复杂。你必须非常小心,才能处理好一个符号,而不是把它从中间拆成两半。

日语是一个很好的例子,因为它们大部分单词中都不仅仅含有字母,还有其他符号,它们的语言有几千个常用符号。一种流行的日语编码是 Shift-JIS ,它需要两个字节来表示一些符号。

我这里已经使用了一些例子,但实际上还有很多其它编码我们正在使用。你不需要在每个程序中都支持所有编码,事实上,我们还有其它的一些原因而不这样做。我们现在需要知道的是有一些不同的编码存在,并且有些人会使用不同的方式来存储数据。现代程序员已经无法忽视这些问题了。

如果你仔细考虑,我确定你一定能找到一些错误编码的例子。是否看到过邮件客户端或者 shell 提示里面大量的标记问题或者奇怪的盒状的字符?通常这些都是数据没有被正确解码的标记。这些导致了数据没有被正确显示,这也是我们正在试图避免的。

记住几个关键点:

1.世界各地的人使用它们自己的方式保存数据。

2.所有的符号都有编码方案来告诉你如果去解释它们。

3.你必须知道数据是否被正确的处理了。

4.有些编码远比其它编码复杂,比如说多字符编码。

5.当你看到垃圾输出,就像错误标记和盒状字符一样,就说明你的程序中有编码错误。

系列文章翻译见 understanding-m17n