编码问题的例子
在windows自带的notepad(记事本)程序中输入“联通”两个字,保存后再次打开,会发现“联通”不见了,代之以“”的乱码。这是windows平台上典型的中文编码问题。即文件保存的时候是按照ANSI编码(其实就是GB2312,后面会详细介绍)保存,打开的时候程序按照UTF-8方式对内容解释,于是就出现了乱码。避免乱码的方式很简单,在“文件”菜单中选择“打开”命令,选择保存的文件,然后选择“ANSI”编码,此时就能看到久违的“联通”两个字了。
在Linux平台上如果使用cat等命令查看文件中的中文内容时,可能出现乱码。这也是编码的问题。简单的说是文件时按照A编码保存,但是cat命令按照当前Locale设定的B编码去查看,在B和A不兼容的时候就出现了乱码。
为什么写这篇文章
中文编码由于历史原因牵扯到不少标准,在不了解的时候感觉一头雾水;但其实理解编码问题并不需要你深入了解各个编码标准,只要你明白了来龙去脉,了解了关键的知识点,就能分析和解决日常开发工作中碰到的大部分编码问题。有感于我看过的资料和文章要么不够全面,要么略显枯燥,所以通过这篇文章记录下笔者在日常工作中碰到的中文编码原理相关问题,目的主要是自我总结,如果能给读者提供一些帮助那就算是意外之喜了。由于严谨的编码标准对我来说是无趣的,枯燥的,难以记忆的,本文尝试用浅显易懂的生活语言解释中文编码相关的(也可能不相关的)一些问题,这也是为什么取名杂谈的原因。本文肯定存在不规范不全面的地方,我会在参考资料里给出官方文档的链接,也欢迎读者在评论中提出更好的表达方式&指出错误,不胜感激。
对编码问题的理解我认为分为三个层次,第一个层次:概念,知道各个编码标准的应用场景,了解之间的差异,能分析和解决常见的一些编码问题。第二个层次:标准,掌握编码的细节,如编码范围,编码转换规则,知道这些就能自行开发编码转换工具。第三个层次,使用,了解中文的编码2进制存储,在程序开发过程中选择合理的编码并处理中文。为了避免让读者陷入编码标准的黑洞无法脱身(不相信?看看unicode的规范就明白我的意思了),同时由于编码查询&转换工具等都有现成工具可以使用,本文只涉及第一个层次,不涉及第二层次,在第三层次上会做一些尝试。在本文的最后提供了相关链接供对标准细节感兴趣的同学继续学习。最后,本文不涉及具体软件的乱码问题解决,如ssh,shell,vim,screen等,这些话题留给剑豪同学专文阐述。
一切都是因为电脑不识字
电脑很聪明,可以帮我们做很多事情,最开始主要是科学计算,这也是为什么电脑别名计算机。电脑又很笨,在她的脑子里只有数字,即所有的数据在存储和运算时都要使用二进制数表示。这在最初电脑主要用来处理大量复杂的科学计算时不是什么大问题但是当电脑逐步走入普通人的生活时,情况开始变遭了。办公自动化等领域最主要的需求就是文字处理,电脑如何来表示文字呢?这个问题当然难不倒聪明的计算机科学家们,用数字来代表字符呗。这就是“编码”。
英文的终极解决方案:ASCII
每个人都可以约定自己的一套编码,只要使用方之间了解就ok了。比如说咱俩约定0×10表示a,0×11表示b。在一开始也的确是这样的,出现了各式各样的编码。这样有两个问题:1.各个编码的字符集不一样,有的多,有的少。2.相同字符的编码也不一样。你这里a是0×10.他那里a可能是0×30。于是你保存的文件他就不能直接用,必须要转换编码。随着沟通范围的扩大,采用不同编码的人们互相通信就乱套了,这就是我们常说的:鸡同鸭讲。如果要避免这种混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII(American Standard Code for Information Interchange)编码,统一规定了英文常用符号用哪些二进制数来表示。ASCII是标准的单字节字符编码方案,用于基于文本的数据。
ASCII最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,已被国际标准化组织(International Organization for Standardization, ISO)定为国际标准,称为ISO 646标准。适用于所有拉丁文字字母。ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。而最高位为1的另128个字符(80H—FFH)被称为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其它符号。
其中:0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
现在所有使用英文的电脑终于可以用同一种编码来交流了。理解了ASCII编码,其他字母型的语言编码方案就触类旁通了。
一波三折的中文编码
第一次尝试:GB2312
ASCII这种字符编码规则显然用来处理英文没有什么问题,它的出现极大的促进了信息在西方尤其是美国的传播和交流。但是对于中文,常用汉字就有6000以上,ASCII 单字节编码显然是不够用。为了粉碎美帝国主义通过编码限制中国人民使用电脑的无耻阴谋,中国国家标准总局发布了GB2312码即中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集——基本集》,1981年5月1日实施,通行于大陆。GB2312字符集中除常用简体汉字字符外还包括希腊字母、日文平假名及片假名字母、俄语西里尔字母等字符,未收录繁体中文汉字和一些生僻字。 EUC-CN可以理解为GB2312的别名,和GB2312完全相同。
GB2312是基于区位码设计的,在区位码的区号和位号上分别加上A0H就得到了GB2312编码。这里第一次提到了“区位码”,我就连带把下面这几个让人摸不到头脑的XX码一锅端了吧:
区位码,国标码,交换码,内码,外码
区位码:就是把中文常用的符号,数字,汉字等分门别类进行编码。区位码把编码表分为94个区,每个区对应94个位,每个位置就放一个字符(汉字,符号,数字都属于字符)。这样每个字符的区号和位号组合起来就成为该汉字的区位码。区位码一般用10进制数来表示,如4907就表示49区7位,对应的字符是“学”。区位码中01-09区是符号、数字区,16-87区是汉字区,10-15和88-94是未定义的空白区。它将收录的汉字分成两级:第一级是常用汉字计3755个,置于16-55区,按汉语拼音字母/笔形顺序排列;第二级汉字是次常用汉字计3008个,置于56-87区,按部首/笔画顺序排列。在网上搜索“区位码查询系统”可以很方便的找到汉字和对应区位码转换的工具。为了避免广告嫌疑和死链,这里就不举例了。
国标码: 区位码无法用于汉字通信,因为它可能与通信使用的控制码(00H~1FH)(即0~31,还记得ASCII码特殊字符的范围吗?)发生冲突。于是ISO2022规定每个汉字的区号和位号必须分别加上32(即二进制数00100000,16进制20H),得到对应的国标交换码,简称国标码,交换码,因此,“学”字的国标交换码计算为:
1 2 3 4 |
|
用十六进制数表示即为5127H。
交换码:即国标交换码的简称,等同上面说的国标码。
内码:由于文本中通常混合使用汉字和西文字符,汉字信息如果不予以特别标识,就会与单字节的ASCII码混淆。此问题的解决方法之一是将一个汉字看成是两个扩展ASCII码,使表示GB2312汉字的两个字节的最高位都为1。即国标码加上128(即二进制数10000000,16进制80H)这种高位为1的双字节汉字编码即为GB2312汉字的机内码,简称为内码。20H+80H=A0H。这也就是常说的在区位码的区号和位号上分别加上A0H就得到了GB2312编码的由来。
1 2 3 4 |
|
用十六进制数表示即为D1A7H。
外码:机外码的简称,就是汉字输入码,是为了通过键盘字符把汉字输入计算机而设计的一种编码。 英文输入时,相输入什么字符便按什么键,外码和内码一致。汉字输入时,可能要按几个键才能输入一个汉字。 汉字输入方案有成百上千个,但是这千差万别的外码输入进计算机后都会转换成统一的内码。
最后总结一下上面的概念。中国国家标准总局把中文常用字符编码为94个区,每个区对应94个位,每个字符的区号和位号组合起来就是该字符的区位码, 区位码用10进制数来表示,如4907就表示49区7位,对应的字符是“学”。 由于区位码的取值范围与通信使用的控制码(00H~1FH)(即0~31)发生冲突。每个汉字的区号和位号分别加上32(即16进制20H)得到国标码,交换码。“学”的国标码为5127H。由于文本中通常混合使用汉字和西文字符,为了让汉字信息不会与单字节的ASCII码混淆,将一个汉字看成是两个扩展ASCII码,即汉字的两个字节的最高位置为1,得到的编码为GB2312汉字的内码。“学”的内码为D1A7H。无论你使用什么输入法,通过什么样的按键组合把“学”输入计算机,“学”在使用GB2312(以及兼容GB2312)编码的计算机里的内码都是D1A7H。
第二次尝试:GBK
GB2312的出现基本满足了汉字的计算机处理需要,但由于上面提到未收录繁体字和生僻字,从而不能处理人名、古汉语等方面出现的罕用字,这导致了1995年《汉字编码扩展规范》(GBK)的出现。GBK编码是GB2312编码的超集,向下完全兼容GB2312,兼容的含义是不仅字符兼容,而且相同字符的编码也相同,同时在字汇一级支持ISO/IEC10646—1和GB 13000—1的全部中、日、韩(CJK)汉字,共计20902字。GBK还收录了GB2312不包含的汉字部首符号、竖排标点符号等字符。CP936和GBK的有些许差别,绝大多数情况下可以把CP936当作GBK的别名。
第三次尝试:GB18030
GB18030编码向下兼容GBK和GB2312。GB18030收录了所有Unicode3.1中的字符,包括中国少数民族字符,GBK不支持的韩文字符等等,也可以说是世界大多民族的文字符号都被收录在内。GBK和GB2312都是双字节等宽编码,如果算上和ASCII兼容所支持的单字节,也可以理解为是单字节和双字节混合的变长编码。GB18030编码是变长编码,有单字节、双字节和四字节三种方式。
其实,这三个标准并不需要死记硬背,只需要了解是根据应用需求不断扩展编码范围即可。从GB2312到GBK再到GB18030收录的字符越来越多即可。万幸的是一直是向下兼容的,也就是说一个汉字在这三个编码标准里的编码是一模一样的。这些编码的共性是变长编码,单字节ASCII兼容,对其他字符GB2312和GBK都使用双字节等宽编码,只有GB18030还有四字节编码的方式。这些编码最大的问题是2个。1.由于低字节的编码范围和ASCII有重合,所以不能根据一个字节的内容判断是中文的一部分还是一个独立的英文字符。2.如果有两个汉字编码为A1A2B1B2,存在A2B1也是一个有效汉字编码的特殊情况。这样就不能直接使用标准的字符串匹配函数来判断一个字符串里是否包含某一个汉字,而需要先判断字符边界然后才能进行字符匹配判断。
最后,提一个小插曲,上面讲的都是大陆推行的汉字编码标准,使用繁体的中文社群中最常用的电脑汉字字符集标准叫大五码(Big5),共收录13,060个中文字,其中有二字为重覆编码(实在是不应该)。Big5虽普及于中国的台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为基准,但厂商又各自增删,衍生成多种不同版本。2003年,Big5被收录到台湾官方标准的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。