软件下载 | 资讯教程 | 最近更新 | 下载排行 | 一键转帖 | 发布投稿
您的位置:最火下载站 > 网络编程 > JSP教程 > JSP/SERVLET中文编码问题的研究总结

JSP/SERVLET中文编码问题的研究总结

烈火建站学院文档 在进行网页编程中我们经常会遇到一些乱码问题,诸如GET/POST提交的数据在服务器端处理时出现乱码,浏览器显示服务器端响应出现乱码等,ajax交互过程中出现乱码,而更糟糕的是每次遇到问题我们都要到网上去搜索一堆的解决方案,但是这些方案大都都捉襟见肘,只能解决部分问题。

分析主要原因,是我们对编码基本知识,java语言对编码处理,浏览器/服务器端在请求响应的过程中对编码的处理,URL的编码规则,还有ajax应用编码规则了解不够透彻等导致的。

本文将从以上这几个原因出发,分析乱码出现的原因和解决方案。

<!--[if !supportLists]-->一、<!--[endif]-->编码基础知识

<!--[if !supportLists]-->1.1 <!--[endif]-->字符、字符集和编码

字符:是文字与符号的总称,包括文字、图形符号、数学符号等。

字符集:就是一组抽象字符的集合。字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集。一组有共同特征的字符也可以组成字符集,比如繁体汉字字符集、日文汉字字符集。字符集的子集也是字符集。

字符和字符集之间的关系可以用下图表示:

<!--[if !supportLists]-->1.2 <!--[endif]-->常用字符集

ASCII:

American Standard Code for Information Interchange,美国信息交换标准码。

目前计算机中用得最广泛的字符集及其编码,由美国国家标准局(ANSI)制定。它已被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。 ASCII字符集由控制字符和图形字符组成。在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1。偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

ISO 8859-1:

ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。

ASCII收录了空格及94个“可印刷字符”,足以给英语使用。但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的变音字母,故可以使用ASCII及控制字符以外的区域来储存及表示。除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使用这个形式来储存及表示。

* ISO 8859-1 (Latin-1) - 西欧语言

* ISO 8859-2 (Latin-2) - 中欧语言

* ISO 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。

* ISO 8859-4 (Latin-4) - 北欧语言

* ISO 8859-5 (Cyrillic) - 斯拉夫语言

* ISO 8859-6 (Arabic) - 阿拉伯语

* ISO 8859-7 (Greek) - 希腊语

* ISO 8859-8 (Hebrew) - 希伯来语(视觉顺序)

* ISO 8859-8-I - 希伯来语(逻辑顺序)

* ISO 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。

* ISO 8859-10 (Latin-6 或 Nordic) - 北日耳曼语支,用来代替Latin-4。

* ISO 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。

* ISO 8859-13 (Latin-7 或 Baltic Rim) - 波罗的语族

* ISO 8859-14 (Latin-8 或 Celtic) - 凯尔特语族

* ISO 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的法语及芬兰语重音字母,以及欧元符号。

* ISO 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。

很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。

Unicode:

Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。 它是http://www.unicode.org制定的编码机制, 要将全世界常用文字都函括进去。 它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。 1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。 但自从unicode2.0开始,unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值,使得两者保持一致。 Unicode的编码方式与ISO 10646的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。 也就是每个字符占用2个字节,基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。

UTF:

Unicode 的实现方式不同于编码方式。

一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。

Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。

* UTF-8: 8bit变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。

* UTF-16: 16bit编码,是变长码,大致相当于20位编码,值在0到0x10FFFF之间,基本上就是unicode编码的实现,与CPU字序有关。

汉字编码:
* GB2312字集是简体字集,全称为GB2312(80)字集,共包括国标简体汉字6763个。
* BIG5字集是台湾繁体字集,共包括国标繁体汉字13053个。
* GBK字集是简繁字集,包括了GB字集、BIG5字集和一些符号,共包括21003个字符。
* GB18030是国家制定的一个强制性大字集标准,全称为GB18030-2000,它的推出使汉字集有了一个“大一统”的标准。

<!--[if !supportLists]-->1.3 <!--[endif]-->为什么会有乱码

在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示",例如GB2312编码的"中文"可以用iso8859-1表示成:"d6 d0 ce c4",utf-8编码的"中文"可以用iso8859-1表示成:" e4 b8 ad e6 96 87"。

Java中出现乱码主要有两个原因:

<!--[if !supportLists]-->l <!--[endif]-->Unicode-->Byte, 如果目标代码集不存在对应的代码,则得到的结果是0x3f。

如:"\u00d6\u00ec\u00e9\u0046\u00bb\u00f9".getBytes("GBK") 的结果是 "?ìéF?ù", Hex 值是3fa8aca8a6463fa8b4.

仔细看一下上面的结果,你会发现\u00ec被转换为0xa8ac, \u00e9被转换为\xa8a6... 它的实际有效位变长了!这是因为GB2312符号区中的一些符号被映射到一些公共的符号编码,由于这些符号出现在ISO-8859-1或其它一些SBCS字符集中,故它们在 Unicode中编码比较靠前,有一些其有效位只有8位,和汉字的编码重叠(其实这种映射只是编码的映射,在显示时仔细不是一样的。Unicode 中的符号是单字节宽,汉字中的符号是双字节宽) . 在Unicode\u00a0--\u00ff 之间这样的符号有20个。了解这个特征非常重要!由此就不难理解为什么JAVA编程中,汉字编码的错误结果中常常会出现一些乱码(其实是符号字符), 而不全是'?'字符, 就比如上面的例子。

<!--[if !supportLists]-->l <!--[endif]-->Byte-->Unicode, 如果Byte标识的字符在源代码集不存在,则得到的结果是0xfffd.

如:

Byte ba[] = {(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1};

new String(ba,"gb2312");( new String(ba,"gbk");输出为"丂啊")

结果是"?啊", hex 值是"\ufffd\u554a". 0x8140 是GBK字符,按GB2312转换表没有对应的值,取\ufffd. (请注意:在显示该uniCode时,因为没有对应的本地字符,所以也适用上一种情况,显示为一个"?"。

<!--[if !supportLists]-->二、<!--[endif]-->Java语言对编码的处理

在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。

<!--[if !supportLists]-->2.1 <!--[endif]-->Byte[] getBytes(String charset)

这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。

<!--[if !supportLists]-->2.2 <!--[endif]-->new String(byte[] bytes, String charset)

这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

相关阅读
网友评论
栏目导航
推荐软件