Chinaunix首页 | 论坛 | 博客
  • 博客访问: 204309
  • 博文数量: 9
  • 博客积分: 1082
  • 博客等级: 少尉
  • 技术积分: 332
  • 用 户 组: 普通用户
  • 注册时间: 2006-01-06 09:41
文章分类

全部博文(9)

文章存档

2011年(5)

2010年(2)

2009年(2)

我的朋友

分类: Delphi

2011-12-12 16:05:58

 

重要提示:本文并非学术文章,本人也并非语言和文字学领域人士,只是出于好奇心,根据自己的理解写下这篇文章。本文的参考文档都来源于互联网,而且并未一一考证其准确性和权威性,因此本文仅供参考。

 

    字符集和字符编码的问题一直困扰着我,之间曾经多次尝试把这个问题理解清楚,但始终由于有些细节问题无法自圆其说因而放弃。网上的资料多数描述过于简单,又或者作者本人对问题也了解不深入,容易产生误导。最近我终于下定决心将之前对“乱码”问题的思考更进一步,否则将始终是一丝遗憾。这里也不得不感叹,老外的“科普”做的好啊,网上有很多质量相当高的文章,表述严密,引用充分,例证丰富,我相信在国内各领域的专家也不少,计算机和语言学方面都有很多有建树的大牛,也许是太忙吧。对我个人而言,最重要的一片文章是“Character set encoding basics”,在本文最后有链接的地址,本人的翻译版本在这里:http://blog.chinaunix.net/space.php?uid=11187&do=blog&id=3034493

 

1.字符集的基本概念

什么是字符集?什么是字符编码?

按照“Character set encoding basics”文中的定义,字符集的编码模型分为以下4个层次

1)抽象字符清单Abstract character repertoire (ACR),无序,无编码;

2)已编码字符集Coded character set (CCS),有序,有编码;

3)字符编码规则Character encoding form (CEF),有序,有编码;

4)字符编码方案Character encoding scheme (CES),有序,有编码,有传输和储存规则(字节序);

这种分层方式,比较偏于学术化,不太容易理解。按我个人的理解,GB2312/GBK/GB18030/ASCII这些字符集编码规则,由于都基于8-bit字节,是属于前三层的,可以认为是三层合一。如果拿Unicode来说明的话,Unicode中定义的所有字符的集合,是第一层;我们通常说的Unicode编码,是指的第二层;现在最常见的UTF-8,是指的第三层。当UCS-4在以8-bit为基础的计算机中存储和传输时,就要涉及字节序的问题,就是第四层,分为big-endianlittle-endian

 

借用“程序员趣味读物:谈谈Unicode编码”中举的一个记事本例子(内容不同):

1)打开记事本(windows自带的那个),输入“我”;

2)另存为 _ansi.txt,注意,编码选择“ANSI”;

3)另存为 _unicode.txt,注意,编码选择“Unicode”;

4)另存为 _unicode_big.txt,注意,编码选择“Unicode Big Endian”;

5)另存为 _utf8.txt,注意,编码选择“UTF-8”;

保存完以后,看一下4个文件的大小,很有意思吧,分别是2/4/4/6个字节,再用二进制方式(推荐使用ultraedit)查看一下其中的内容:(高位字节在前)

1ansi:CE D2

2unicode:FF FE 11 62

3unicode_big:FE FF 62 11

4UTF-8:EF BB BF E6 88 91

第一个文件,ansi,比较好解释,2字节,就是GB2312/GBK/GB18030编码,即简体中文windows的默认内码

第二个文件,unicode,就是Unicode编码,“我”的编码是0x62 0x11,不过前面多了2字节的前导符,FF FE,表示为little-endian

第三个文件,unicode_big,也是Unicode编码,不过前导符变为FE FF,表示big-endian

第四个文件,UTF-8,是在Unicode基础上的二次编码,分别将FE FF(big-endian)62 11进行了二次编码,详细编码过程参见“程序员趣味读物:谈谈Unicode编码

 

 

常见字符集(字符编码规则)

ASCII,读作阿斯克码,7bit表示,美国国家标准信息编码,是最常用英文字母和符号、数字的集合及编码;它的常见别名是ISO 8859-1 Latin1

EASCII,扩展ASCII码,完整的利用一个字节,在ASCII的基础上扩展了一些不常用字符

GB2312,国标中文字符编码,1980年制定并颁布;

GBK,国标码,1995年

GB18030,国标码,2000年

以上这三个编码标准都是向下兼容的,兼容的意思有两方面,其一是指字符的集合,其二是指编码。另外,在微软操作系统中(其实也影响到了Linux领域),经常出现“代码页”(code page)的概念,这些代码页,只是微软自己的定义,可以理解为CP936=GBK。

UnicodeUTF-8,我原来一直以为这两个东东是一回事,后来发现其实理解错了,UTF-8可以理解为是以Unicode为基础进行二次编码的,详见这篇文章:“程序员趣味读物:谈谈Unicode编码”,

 

 

2.关于乱码的思考

什么是乱码

个人认为,如果在储存或传输过程中,计算机中的信息不能被正常解析,从而导致在信息展示的时候出现无法被正确理解的情况,可以认为出现了“乱码”。常见的乱码有两种表现形式:

 

1)部分中文字符能够正常展示,另外的中文字符被展示为方框;

       这种情况多数是由于缺少相应的字体支持,例如,在虚拟机上安装完linux之后,如果没有安装图形界面,默认的字符窗口其实是没有相应的字体支持的,这时的中文只能显示为方框,安装zhcon以后才能够正常展示GBK/UTF8的中文字符。

       还有一个场景,部分网页上的字符,并不能被所有浏览器支持,或者该浏览器对某种编码方式的支持不完整,会出现部分字符展示为方框的情况。

       另外,如果能够以GBK/GB2312正常展示的网页,如果手工将encoding变更为utf-8,则所有中文字符都会变成方框。

 

2)几乎所有字符都不能正常展示,许多字符被显示为“?”,或者被显示为一大堆不可理解的古怪字符;

       这种情况很可能是由于字符编码不配套,需要具体分析。例如,在浏览器中能够正常显示的页面,如果将其编码更改为其他不兼容的编码,则很多会展示为“?”和乱七八糟字符的组合

 

乱码产生的原因

产生乱码的原因很复杂,也正是这个原因导致了对乱码问题的分析很难全面和彻底。但是,综合我目前遇到的乱码问题来看,只要将字符展示的过程剖析清楚,一段段的调整,总能找到解决的办法。

 

字符在计算机中,都是以二进制的方式进行存储的,而且文本本身是不能够标识它使用的编码方式的。也就是说,同一段二进制字节流,可以用很多种不同的编码方式去解码,然后根据解码后的结果(也是二进制字节流),在操作系统中按照预定义好的字体进行展示。所谓字体库,或者字库,其实就是数字和相应展示方式(点阵、truetype等)的组合。计算机本身是不会“体会”到“乱码”的发生的,它只是按照用户选定的字体,根据不同的数字进行展示而已,无论展示的结果如何,都只有人才能判断“乱码”与否。乱码的产生,其实只有两个原因,一是没有使用正确的解码规则来解释字节流,二是使用了错误的展示字体。实际应用当中,编码规则的问题居多。

 

单字节的编码通常情况下不会出现乱码的问题,特别是英文字符,而双字节由于多数情况下编码规则复杂,另外存在中间截断的问题,会比较复杂。从产生问题的渠道来看,常见的有以下几类:

 

1)网页展示乱码

    多数情况下,可以通过更改页面编码方式来解决。少数情况下,浏览器本身处理多语言字符集有缺陷的时候,无论怎样修改编码方式,都不能彻底解决乱码问题。例如,截至本文定稿,IE9就存在部分UTF-8中文编码无法解析的问题,同样的网页在Chromefirefox中都没有问题。

 

2UNIX/LINUX终端显示乱码

2.1)终端的中文环境;

    如果没有合适的中文环境(字库支持),无论解码方式如何正确,也不可能正常展示中文。在常用的终端工具中,例如:Xshell/Secure CRT/Putty,都可以设置终端的字符编解码方式,通常设置的值有两个系列:

    其一,GB2312/GBK/GB18030/CP936/ANSI/Default等,其实都是兼容的编码,或者仅仅是名称不一样;

    其二,UTF-8,这个是在互联网上最常见的编解码方式了;

    另外,如果不是windows下的终端工具,而是系统自身的字符终端,则可以安装字符终端专用的中文环境,例如linux下的zhcon

 

2.2)cat显示文本文档内容

    通过类似cat命令的方式显示纯文本文档的内容,通常只受一个因素的影响,即终端的工具的字符编码方式,常用工具中都可以进行设置。只要文本内容的编码方式与终端的编码方式一致(或兼容),则一定不会出现乱码。

 

2.3)命令行的中文提示(CLI

    命令行接口Command Line Interface的提示语言,是通过环境变量进行设置的,好几个变量都可以设置,但优先级有区别,其中LC_ALL > LC_XX > LANG,如果想用中文显示提示信息,可以这样设置:

    export LC_ALL=zh_CN.gbk

    其中zh表示使用中文输出提示信息,gbk表示使用GBK编码方式输出中文提示信息,这个编码方式要与终端的设置一致或者兼容才可以正常显示;

 

2.4)输入中文信息

    Shell环境输入中文,与vi/vim这种编辑器的情况稍有不同,编辑器的情况放到下一节说明。按照一般的理解(我原来就是这样理解的),只要能正常显示中文的地方,一定能够正常输入中文。但是,实测的情况略有不同,详见下面的表格。

    输入中文信息,我暂时只考虑了以下三种情况:

    a)在SHELL命令行中输入中文

           这种场景下,如果终端字符集是GBKLC_ALLUTF-8时,输入的中文字节流乱序(第一个中文字符的高字节被放到字节流的末尾),无法正常展示。

    b)使用cat等方式输入中文,并重定向到文件中

           这种场景下,任何时候,都能够正常输入中文

    c)使用文本编辑器,详见下一节描述

    与仅仅显示中文信息不同,输入中文的时候实际上经历了更多的步骤。最开始从终端工具中输入中文编码字节流,然后经过网络协议传输到服务端,服务端收到字节流以后,根据终端设置的情况,再推送显示信息到终端工具,终端工具进行呈现。在SHELL命令行中输入中文不正常的情况,很有可能是由于服务端的处理逻辑不健全。

 

2.5)文本编辑器,例如vim

    文本编辑器的种类很多,emacs/vi等,vi的版本也很多,各个主流UNIX平台的商业版本实现都不相同,还有vim。本文暂以vim为例子进行说明,其他编辑器的情况应该是类似的。可以参考:“”,http://blog.chinaunix.net/space.php?uid=20147410&do=blog&id=3018800

 

    遗憾的是,这篇文章并未给出vim处理这三个内部变量的顺序,经摸索,顺序应该为:

    1fileencoding

    2endocing

    3termencoding

    这三个内部变量均可以通过set xxx=xxx的方式进行设置,并可以通过setset all进行查看。另外,上面提到的博文中是使用LANG变量来改变vimencoding变量值,但由于LANG的优先级最低,实际使用过程中,使用LC_ALL的效果最好,当然其实也可以直接在vim中使用set进行设置。

 

    个人理解,如果仅仅从输入和输出(显示)的角度来看,其实vim等文本编辑工具并没有必要设置3个不同的变量来进行处理,这大概也是大多数商用unix平台的vi版本都没有类似设置的原因。提供3个变量的原因在于,vim试图提供一些编码转换的方式,例如,通过设置fileencoding变量,可以改变vim写入和读出使用的编码,而termencoding仅仅改变显示时使用的编码方式,而encoding其实只是提供缓冲。这与数据库的字符集(编码)处理方式是类似的。也可以这样理解,无论这三个变量设置为何值,其实并不见得不会影响数据的输入和展示。例如,在我们输入输出中文信息的时候,即便fileencoding=encoding=termencoding=iso8859(英文字符集),只要文本文件的编码方式与终端的编码方式一致(兼容),比如都是GB2312,文本信息都可以正常展示和输入。

 

附表:不同设置情况下的中文显示结果

服务端环境:ubuntu 11.10 服务器版

客户端环境:中文win7+Xshell 4

 

文件编码 终端字符集 LC_ALL cat输出 SHELL输入 VIM相关 备注
vim显示 vim输入 encoding fileencoding termencoding
GBK GBK GBK 正常 正常 正常 正常 GBK GBK
UTF-8 GBK GBK 乱码 正常 正常 GBK UTF-8
GBK UTF-8 GBK 乱码 正常 乱码 乱码 GBK 如果将encoding或termencoding改为utf-8,则可以正常显示
UTF-8 UTF-8 GBK 正常 乱码 乱码 GBK UTF-8 如果将encoding或termencoding改为utf-8,则可以正常显示
GBK GBK UTF-8 正常 乱码 乱码 乱码 UTF-8 latin1 如果将encoding或termencoding改为latin1,则可以正常显示;或者将encoding和fileencoding改为GBK(cp936)
UTF-8 GBK UTF-8 乱码 乱码 乱码 UTF-8 utf-8 如果将encoding或termencoding改为cp936,则可以正常显示
GBK UTF-8 UTF-8 乱码 正常 乱码 乱码 UTF-8 latin1 如果将fileencoding改为cp936,则可以正常显示
UTF-8 UTF-8 UTF-8 正常 正常 正常 UTF-8

 

 

 

3)数据库乱码

    数据库中与编码/字符集相关的设置主要有两个,一个是数据库本身的编码,另一个是客户端环境的编码。网上有很多关于数据库乱码问题的讨论,多数并没有涉及到问题的本质。数据库中保存的数据,其实与文件方式保存的数据没有什么两样,都只是字节流而已,而字节流本身通常是不能自我标识的,例如,如果仅仅根据二进制的编码,无法判断出它的内容是采用GB2312编码,还是EASCII编码,或者是一个图像信息。也许,正是由于字节流无法标识自己,因此需要有一个参数来标识数据库使用的文字编码。在客户端与服务端的编码设置统一的时候,无论在数据库的字段中存储什么样的数据,都是不影响数据的储存和展示的,原因是,不会发生编码转换。

 

    例如,网上很多帖子讨论到乱码问题的时候,给出的建议都是,将数据库的字符集设置为utf-8,这当然不会有什么问题,utf-8编码是被最广泛使用的编码标准,所以支持也相当完备,特别是utf-8编码几乎可以被所有软件“识别”出来(特征码)。这样一来,实际上掩盖了编码的问题。其实,如果仅仅为了储存和展示中文信息,将数据库的字符集设置为iso-8859-1(单字节)编码,客户端的语言环境也设置为同样的编码方式,存取中文数据,也不会有任何乱码的情况发生。之所以产生乱码,是由于在某些地方出现了编码方式的不匹配。

 

    比如,数据库的编码设置为GBK,但是客户端的设置为UTF-8,那么如果在客户端使用UTF-8的编码方式输入中文数据,当客户端软件发现这种不一致时,会执行从UTF-8GBK的编码转换,然后通过网络插入到数据库的具体字段中。当这段数据被读取时,如果客户端的设置为UTF-8,那么同样要发生GBKUTF-8的转换,最终以UTF-8的形式展示数据。但是,如果数据被读取时,客户端的设置为GBK,则数据无需转换就可以以GBK的形式直接呈现,然而,如果客户端是设置为UTF-8编码的网页,但使用GBK方式访问数据库,那么数据被最终呈现时就会出现乱码。

 

    总之,数据库提供设置数据库和客户端编码方式的选项,只是为了更好的提供编码转换工作,并不是必需的,无论设置成何种编码方式,与实际存储在字段中的数据都没有必然联系,只是会在编码转换的时候提供方便,否则,这些转换工作就只能完全交给客户端来完成。

 

3.尚未解决的疑问

 

1)关于windows剪贴板的实现机制中,是否包括了编码转换

  从现象上来看,当从一个ansi编码的文本中拷贝中文字符,再到utf-8编码的文本中进行粘贴,没有出现乱码,但是这两种编码方式是不同的,也就是说,必然在这个过程中出现了编码转换,个人怀疑是利用剪贴板进行复制的时候,进行了编码转换,将复制的文本保存为操作系统内码,然后粘贴的时候由应用程序进行内码到utf-8编码的转换,完成粘贴。

 

2)输入法输入不同编码的文本时,采取什么机制?

  当打开一个cp936编码的文件进行编辑时,输入法的输出是cp936编码的,但打开一个utf-8编码文件进行编辑时,输入法的输出变成了utf-8的,输入法是如何知道什么时候应该使用什么编码的?个人猜想,有可能输入法的输出只是操作系统的内码,在文本编辑器中进行内码到其他编码的转换。

 

 

4.参考资料

 

 

1)“程序员趣味读物:谈谈Unicode编码”,

2“Character set encoding basics”,

3)“”,http://blog.chinaunix.net/space.php?uid=20147410&do=blog&id=3018800

4)“Java应用中的汉字乱码问题分析”,计算机技术与发展,20061月第1期,刘长生等

阅读(14512) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~