闫 静, 王天宝, 罗 浩
(成都信息工程学院,四川成都 610225)
程序员在开发Web应用的时候经常会遇到中文乱码问题,虽然知道是编码问题,改变编码就可以解决中文乱码问题,但是很多时候并不清楚为什么要这样做,这一直困扰着许多开发者。所以了解字符编码,并搞清楚中文乱码产生的原因,是解决中文乱码的最有效方法。
计算机中的信息包括数据信息和控制信息,数据信息又可分为数值和非数值信息。非数值信息和控制信息包括了字母、各种控制符号、图形符号等,它们都以二进制编码方式存入计算机处理,这种对字母和符号进行编码的二进制代码称为字符代码。计算机存放时不是存储字符本身,而是存储字符相对应的二进制表示,即每个字符在字符编码集中的编号[1]。从字符编码的发展历程分析,大致可以分为3个阶段:ASCII编码、ANSI编码、Unicode编码。
美国信息互换标准代码(American Standard Code for Information Interchange,ASCII)是基于拉丁字母的一套电脑编码系统。主要用于显示现代英语和其他西欧语言。是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。ASCII码使用指定的7位或8位二进制数组合表示128或256种可能的字符。标准ASCII码也叫基础ASCII码,使用7位二进制数表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符[2-3]。在标准ASCII中,其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。后128个称为扩展ASCII码,目前许多基于x86的系统都支持使用扩展(或“高”)ASCII。扩展ASCII码允许将每个字符的第8位用于确定附加的128个特殊符号字符、外来语字母和图形符号。ASCII码在内存中存储字符串的方式如表1所示[4]。
如英文字符串“GOOD”在内存中的存储方式为:
表1 ASCII码在内存中存储字符串的方式
为使计算机支持更多语言,通常使用0x80~0xFF范围的2个字节表示1个字符。比如:汉字‘中'在中文操作系统中,使用[0xD6,0xD0]这两个字节存储。不同的国家和地区制定了不同的标准,由此产生了GB2312,BIG5,JIS等各自的编码标准。使用2个字节代表一个字符的各种汉字延伸编码方式,称为ANSI编码。同时为了保持与ASCII码的兼容,约定第一个字节的最高位为0时(<128),其编码表示与ASCII码相同,而最高位为1时(>128),其与后面的一个字节共同表示一个扩展语言字符。按照这种定义方式不同国家和地区制定了不同的标准,由此产生了简体中文GB2312、GBK、繁体中文BIG5、日文SJIS等字符集。在简体中文系统下,ANSI编码代表GB2312编码,在日文操作系统下,ANSI编码代表JIS编码。
不同ANSI编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段ANSI编码的文本中。这些从ASCII扩展的编码方式,英文部分都是兼容的,但扩展部分的编码由于采用不同的方式制定,它们是不兼容的,虽然很多字在3种体系中写法一致(例如“中文”这两个字),但在相应字符集中的坐标不一致,所以GB2312编码的字符用BIG5看就全是乱码了。另外在浏览其他非英语国家的页面时(比如包含有德语的人名时)经常出现奇怪的汉字,是由扩展位的编码冲突造成。
对于中文来说常用的是gb2312编码,它是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集——基本集》,由国家标准总局发布,1981年5月1日实施,通行于大陆。新加坡等地也使用此编码。它是计算机可以识别的编码,适用于汉字处理、汉字通信等系统之间的信息交换。基本集共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位[5]。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码。gb2312编码每个汉字及符号以两个字节表示。第一个字节称为“高位字节”(也称“区字节)”,第二个字节称为“低位字节”(也称“位字节”)。“高位字节”使用了0xA1~0xF7(把01~87区的区号加上0xA0),“低位字节”使用了0xA1~0xFE(把01~94加上0xA0)。由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0~0xF7,“低位字节”的范围是0xA1~0xFE,占用的码位是72*94=6768。其中有5个空位是D7FA~D7FE。例如“啊”字在大多数程序中,会以两个字节0xB0(第一个字节)0xA1(第二个字节)储存。
为了使国际间信息交流更加方便,Unicode字符集编码诞生。Unicode是Universal Multiple-Octet Coded Character Set的缩写,中文含义是“通用多八位编码字符集”。是由一个名为Unicode学术学会(Unicode Consortium)的机构制订的字符编码系统,Unicode目标是将世界上绝大多数国家和地区的文字、符号都编入其字符集,为每种语言中的每个字符设定了统一并且唯一的二进制编码(位模式),以满足跨语言、跨平台进行文本转换、处理的要求,以达到支持现今世界各种不同语言的书面文本的交换、处理及显示的目的[6],使世界范围人们通过计算机进行信息交换时达到畅通自如而无障碍。即Unicode编码就是先将世界上存在的绝大多数常用字符纳入Unicode字符集,然后进行统一排号。而每个Unicode字符的编码(位模式)就是该字符在Unicode字符表中的序号,所以与上面提到的ANSI编码不同的是,一个Unicode字符的编码用一个整数表示,而这个整数的长度通常>=2个字节。事实证明,对可以用ASCII表示的字符使用Unicode并不高效,因为Unicode比ASCII占用大一倍的空间,而对ASCII来说高字节的0对其毫无用处。为了解决这个问题,就出现了一些中间格式的字符集,被称为通用转换格式,即UTF(Universal Transformation Format)。目前存在的UTF格式有:UTF-7,UTF-7.5,UTF-8,UTF-16,以及UTF-32。目前比较常见的UTF方案有3种:
UTF-16:其本身就是标准的Unicode编码方案,又称为UCS-2,固定使用16 bits(两个字节)整数表示一个字符。
UTF-32:又称为UCS-4,固定使用32 bits(四个字节)整数表示一个字符。
UTF-8:最广泛使用的UTF方案,UTF-8使用可变长度字节来储存Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。UTF-8更便于在使用Unicode的系统与现存的单字节的系统进行数据传输和交换。与前两个方案不同:UTF-8以字节为编码单元,没有字节序的问题。
在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起,都是直接将字符和最终字节流绑定,例如ASCII编码系统规定使用7比特来编码ASCII字符集;GB2312以及GBK字符集,限定了使用最多2个字节编码所有字符,并且规定了字节序。这样的编码系统通常用简单的查表,也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。这种方式的缺点在于,字符和字节流间耦合太紧密了,从而限定字符集的扩展能力。因此Unicode在设计上考虑到这一点,将字符集和字符编码方案分离开。也就是说,虽然每个字符在Unicode字符集中都能找到唯一确定的编号(字符码,又称Unicode码),但是决定最终字节流的却是具体的字符编码[7]。例如同样是对Unicode字符“A”进行编码,UTF-8字符编码得到的字节流是0x41,而UTF-16(大端模式)得到的是0x000x41[8]。
通过前面的介绍,了解了计算机中编码的大致分类。在实际开发过程中,没有必要去深究每一种编码具体把一个字符编码成哪几个字节,只需要知道所谓“编码”就是把字符转换为若干个字节的数字就可以了。
中文字符乱码在Web应用开发中一直困扰着许多开发者。出现乱码的原因一般是字符编码标准不统一造成[5]。标准ASCII码也叫基础ASCII码,使用7位二进制数表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。各国通过ANSI编码扩展了适合自己国家的字符编码标准,ANSI编码使用2个字节表示一个字符,各种ANSI编码是不兼容的。Unicode字符的编码用的是一个整数表示,而这个整数的长度通常>=2个字节。因此计算机中存储的同一组编码采用不同的字符编码解析就会出现不同的结果,就会出现乱码。通过以上内容知道产生字符乱码最主要的原因就是在开发的各个环节或阶段中使用的字符集不一致或者前后不兼容,从而使字符在被调用和显示过程中不能被下一阶段正确的识别,所以出现了乱码[4]。
对于Web应用解决乱码一般从以下3方面入手,第一,首先检查网页文件本身存储时使用的字符编码和网页声明的字符编码是否一致,如果不一致就会导致乱码,那么把它改为一致就能够解决乱码问题了[9]。如果文件比较少,可以采用文本文档的另存为改变文件本身存储时使用的字符编码,在文件比较多的情况下,可以使用转换工具批量转换文件本身存储时使用的字符编码。第二,检查网页内是否使用META HTTP-EQUIV标签指定了字符编码,如果没有为其指定合适字符编码即可。因为如果不为其指定字符编码,那么它会使用默认的字符编码,此时若和文件存储时使用的字符编码不一致就会导致乱码。第三,检查服务器返回的响应头Content-Type是否指明字符编码 ,如果没有为其指明正确匹配的字符编码。
早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码,936代码页,实际上表达的是同一个意思。但是对于Unicode则不同,Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UTF-16等具体编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,可以写gb2312,utf-8,utf-16等,但请不要写Unicode例如在网页的meta标签里写charset=Unicode[7]。
当程序使用特定字符编码解析字节流的时候,一旦遇到无法解析的字节流时,就会用□替代。因此,一旦最终解析得到的文本包含这样的字符,而又无法得到原始字节流的时候,说明正确的信息已经彻底丢失了,尝试任何字符编码都无法从这样的字符文本中还原出正确的信息[7]。此时要重新输入中文信息,并使用正确的编码才可以正确显示。
介绍了字符编码标准,分析了中文乱码产生的主要原因,提出了解决办法。对Web应用开发中浏览器端的相关中文乱码问题分析仍然还是很有限的,另外没有介绍服务器端中文乱码问题。
[1] 张峰.Java Web应用中编码问题的研究与解决[J].计算机与网络,2008,(28).
[2] 杨玉婷.Web应用程序开发中的中文乱码问题讨论[J].重庆三峡学院学报,2011,(3):60-63.
[3] 程晓锦.应用程序开发中的乱码问题[J].北京印刷学院学报.2011,(8):60-62.
[4] 高菲.Web开发中乱码问题的开发与解决[J].科技管理研究,2010,(8):124-125.
[5] 陈小瀚.中文编码原理及其乱码问题的探讨[J].计算机与信息技术.2007,(24).
[6] 苏蕴.关于Java Web技术开发中中文乱码问题的深入探讨[J].福建电脑,2010,(8):142-143.
[7] 关于字符编码你所需要知道的[EB/OL].http://www.cnblogs.com/KevinYang/archive/2010/06/18/1760597.html
[8] 刘财兴,孙微微,肖德琴.Java编程技术中汉字“乱码”问题的分析及解决[J].广东交通职业技术学院学报,2002,(6):56-61.
[9] 杨金花.JSP技术中文乱码的原因及解决办法[J].电子设计工程,2011,(1):25-27.