2009年2月21日星期六

“曹操”为什么会变成“变巨”?


以前在PC上玩过三国志的人大多数都会记得一个奇怪的词——“变巨”。但大家都知道,那是曹操,因为头像放在那,一看就知道是曹操,一张枭雄的脸。可是为什么会显示成“变巨”呢?有些人会说,编码问题呗。是的,那到底是怎么回事儿呢?

这个问题的根本原因可以说是编码和解码方式不匹配

要解释这个问题,首先要解释下Code Page的概念。在Windows下,打开控制面板->区域和语言选项->高级,里面就有代码页转换表。我们在里面会看到类似936  (ANSI/OEM - 简体中文 GBK) 这样的选项。这就是一个Code Page(代码页)。

Code Page最早始于IBM,当时个人PC上只有ASCII码,用0x00-0x7F这128个code point(代码点)来编码控制字符,数字,大小写字母,特殊字符和打印符号等。但是很快ASCII码就无法适应需求了,更多的西欧字符要求被加入码表。于是出现了EBCDIC codepages。这些Code Pages扩充了ASCII码,成为256位的一个码表。前面0x00-0x7F与ASCII码保持一致,后面的128根据不同的语言定义不同的code page。微软一开始借用了这些CODE PAGE,之后用自己的ANSI(或者说ISO 8859-1)的code page作为补充,成为现在我们在Windows操作系统上的CodePage。

在Windows操作系统中,对CJK(Chinese,Japanese,Korean) 也用Code Page进行处理。但是中文等语言的字符实在太多,不可能用256位的code page来表示,那怎么办呢?一个直观的想法是用双字节来编码一个字符,理论上就可以安置65536个字符。也可以用多字节来表示,单字节部分可以兼容ASCII码,双字节部分编码CJK字符。在code page设计时是怎么考虑的我不知道,但最后还是采用了后一种方式。

看下图:


这是一张Windows Codepage 936(以下简称CP936)的示意图。前面0x00-0x7F和ASCII码一致,和Windows上其他的codepage保持一致。后面128个字节被称为lead byte,用来和另一个byte组合成一个CJK字符的编码。每个lead byte又对应一张子表,比如0x81这个lead byte对应的子表如下图所示:


于是汉字“丢”的GBK码就是0x8147。但是子表并不一定都是256个字符。理论上GBK码按照这种方式可以编码128*256个=32768个字符。但实际上中文字符要多的多。因此出现了其他更好的编码,这里先按下不说。Windows的设计的这个字符集被称为MBCS(Multi Byte Character Set)。

知道了codepage的概念,我们就可以解释“曹操”-“变巨”的问题了。为什么我开头说“编码和解码方式不匹配”呢?想想看,曹操是存放在什么地方的?用什么编码存放的?

对了,曹操两个字是存放在资源文件里面的。而我们玩的三国志是第三波汉化的繁体中文版,也就是说,曹操两个字是以BIG5码(CP950)进行编码的。从以下的CP950_B1可以看到,“曹”字的编码是:B1E4。而我们是在简体中文的Windows上玩这个游戏的,对于磁盘上存放的B1E4,我们用CP936 GBK码来解码的。从下面的CP936_B1可以看到B1E4正好就是“变”字。



同理可以知道操对应的是巨,繁体字赵云对应的简体字是化冻。

事实上,这小小的一个问题,牵涉到Locale,字符集,codepage,编码等各种问题。
而CodePage在使用过程中也暴露出很多问题;最主要的就是一个代码点(code point)在不同的codepage中代表不同的字符,变成了一对多的关系,导致在一个程序中没法使用多语言。不同的厂商在设计code page时缺少文档,不利于交流等。

解决这些问题的,就是Unicode。我会在最近的blog中详细介绍Unicode。因为Unicode是每个程序员需要了解的概念。

最后,我总结下Code Page的概念:
  • Code Page只在Windows和IBM的mainframe上有,Linux/Unix等操作系统并没有这个概念。
  • Code Page是为了解决多语言及本地化的问题而产生的。
  • 使用Code Page,无法在一个程序中支持多语言。
  • 在Windows中,一旦换了Code Page,我们需要重启。
  • Code Page提供了字符集和编码/解码方式。

更新于2009.2.28: 关于unicode的介绍已经写好了--Unicode, a programmer should know

没有评论: