J2EE Web组件中中文及相关的问题(一)

类别:Java 点击:0 评论:0 推荐:

“与C/C++不同,Java中的字符数据是16位无符号型数据,它表示Unicode集,而不仅仅是ASCII集”①。这是一个很好的做法,它解决了www上更多的程序设计问题,比如说低成本的国际化(International),然而用16位的字符,却带来了浪费,毕竟Java所处理的信息,绝大多数都是英文,对它们来说7位的ACSII码已经足够了,而Unicode却需要双倍的空间,所以Java的这种兼顾各种语言的做法却是与存储资源及效率的妥协。而对中国的Java程序员(特别是初级的)来说,Java采用Unicode字符,却给我们带来了尴尬甚至噩梦——Web页面上显示的不是中文,而是乱码。

一、        常见字符集简介

    字符集就是字符内码到字符的表现形式之间的映射的集合。ASCII字符A是就内码0x41的表现形式,所以在很多程序语言中,字符变量和整型变量仅在一念之差。

1.        ISO8859系列

ISO8859包括诸如ISO8859-1,ISO8859-2之类的一系列字符集,它们都是8位的字符集,0~0x7F仍与ASCII字符集保持兼容,大于0x7F的是各种拉丁字符或欧洲字符的扩展。

2.        GB2312字符集

如果像ISO8859系列一样,大于0x7F的字符用来表示汉字,则最多表示128个,这显然不够,于是就有了GB2312标准所产生的字符集,如果当前字节(8 bit)小于0X80,则仍当它为英文字符;如果它大于等于0x80,则它和紧接着它的下一个字节构成一个汉字字符,这样,GB2312字符集可包含大约4000多个常用简体汉字和其他汉字中的特殊符号(如①㈠之类)。其他类似的汉字字符集还有GBK(GB2312的扩展),GB18030,Big5(繁,台湾省用),详细规范介绍可参考:http://www.unihan.com.cn/cjk/ana17.htm

3.        Unicode字符集

Unicode字符最初是16位的(出于需要,后来增加了代用对),它和7位的US-ASCII保持兼容,MS的Windows NT/2000/XP和Sun的Java都用它作为默认的字符集,它最初是美国商务联盟的事实上的标准,它遵循国际通用字符(UCS)集标准:ISO/IEC 10646。Unicode的主要目标是提供一个“通用字符集”,这个通用字符集包括世界上所有的语言,字母和文字,所以在Unicode字符集中,不光“I”是字母,“我”也是字母,在写Java时也可以“int 我是中国人 = 0xff;”。毕竟16位的Unicode字符集最多只有216= 65536个字符,还不足以在实际应用中表示所有的字符,而且在以英文为主要信息的互联网时代,它的使用、存储与传输,都极其浪费空间,所以在此基础上出现了UTF-8(Unicode Transformation Form 8-bit form)和UTF-16这两种对Unicode字符编码的规范,在UTF-8中,属于US-ASCII中的字符,仍用一个字节表示,且和US-ASCII兼容,编码其他的字符,则用1(大于0x7F部分)到3个字节。UTF-8的变长性和复杂性,对非ASCII的字符,就不大友好了,也开始违背了Unicode的初衷。而UTF-16则是很简单的编码方式,它完全遵循Unicode标准,用16位的定长空间来表示部分Unicode字符集。关于Unicode的更多规范,请访问Unicode联盟站点:http://www.unicode.org,UTF-8和UTF-16分别定义在IETF的RFC 2279和RFC 2781中,可以通过http://www.ietf.org/rfc2279.txt或http://www.ietf.org/rfc2781.txt访问它们。

一般情况下,字符集名称是大小写不敏感的,所以GB2312也可以写作gb2312或Gb2312。

二、        乱码带来的尴尬

1.        先看一个JSP

JSP(Java Server Page)的实质还是一个Servlet,所以用JSP,也可以说明Servlet中的一些问题,就一般而言,JSP代码比Servlet代码还要简单。

我们先用JSP来做一个实验,下面的这个JSP文件中含有常量字符串“我是中国人”,看看它在浏览器的输出是否会是乱码?

<%-- discomfiture.jsp --%>

<%

 String str = "我是中国人";

 System.out.println(str);

 out.println(str);

%>

从浏览器打开它,并没有乱码,显示的就是“我是中国人”这个字符串。先别乐,再看看服务器的输出窗口吧,如图2-1,服务器监视窗口输出了乱码(红色下划线标出)。

图 2-1 服务器窗口中输出的乱码

    虽说只是在服务器端出现了乱码,而客户端浏览器是完全正确显示的,但这里很明显出了什么问题,否则都两边应该是正确的输出。

服务器端输出了乱码,说明服务器Java虚拟机(Java Virtual Machine, JVM)没有“得到”正确的字符串。为了保证JVM能够正确“得到”我们指出的含中文的常量字符串,我们可以直接用字符的Unicode内码代替字符串中的字符,就像

String str=”I am Chinese”;

String str = “\u0049\u0020\u0061\u006D\u0020\u0043\u0068\u006E\u0065\u0073\u0065”;

代替一样。明确给JVM指出这些字符串,是否还会出现乱码呢?要得到一个字符的Unicode内码是件很容易的事,Java和JavaScrtipt的字符都是用Unicode字符集的。先看看输出Unicode字符内码的Java程序:

public class getCode

{

                   public static void main(String args[])

                   {

                            char chs[] = args[0].toCharArray();

                            for(int i = 0; i < chs.length; i++){

                                      System.out.println(chs[i] + " = " + (int)chs[i]);

                            }

                            System.out.println(args[0]);

                   }

}

编译并执行它,结果如图:

图 2-2 JVM输出

不过JavaScript用起来,怎么也比Java来得快,这里也介绍一段JavaScript代码:

<script>

var str = "我是中国人";

for(var i = 0; i < str.length; i++)

{

          document.wirte(str.charAt(i) + " = " + str.charCodeAt(i) + "<br>");

}

document.write(str);

</script>

保存为HTML文件,输出如下图:

图 2-2 JavaScript在IE6.0中输出

现在替换discomfiture.jsp中的中文字符串:

<%-- discomfiture1.jsp --%>

<%

 String str = "\u6211\u662F\u4E2D\u56FD\u4EBA";

 System.out.println(str);

 out.println(str);

%>

    实验得到了希望的结果,服务器端输出窗口正确输出了字符串,可客户端浏览器却又输出了乱码,如下图:

图 2-3 JSP在IE6.0中出现了乱码

图 2-4 客户端刷新两次的服务器窗口的输出

因为直接使用的Unicode码生成的字符串,保证了在JSP生成的Servlet discomfiture1$jsp中,字符串str的值一定是“我是中国人”,而服务器窗口的输出也证实了这一点,那么也就是说在Servlet discomfiture$jsp中str的值并不是“我是中国人”,因为它在服务器窗口中输出了乱码,如图 2-1可以发现,当时输出了10个字符,即str的长度是10,并不是5。可为什么在浏览器却好好地得到了这个字符的输出呢? 先简要明白两个概念:编码与解码。

2.        编码与解码

编码(Encode)和解码(Decode)是两个相反的动作。编码是把字符按照某种映射标准(字符集),转换成字节,这时我们把执行编码动作时所采用的标准叫编码(encoding)。如我们对Unicode字符串

”我是中国人”

按照GB2312标准编码(byte bsg[] = ”我是中国人”.getBytes(“GB2312”);),就可以得到一个字节序列(bytes sequence),用十六进制的码值表示:

0xCE0xD20xCA0xC70xD60xD00xB90xFA0xC80xCB

按照UTF-8标准编码(byte bsu[] = ”我是中国人”.getBytes(“UTF-8”);),就可以得到字节序列:

0xE60x880x910xE60x980xAF0xE40xB80xAD0xE50x9B0xBD0xE40xBA0xBA

而解码则是将字节序列按照某种字符标准(解码,decoding),转换成字符串。如我们对字节序列:

0xCE0xD20xCA0xC70xD60xD00xB90xFA0xC80xCB

按照GB2312解码(new String(bsg,”GB2312”),或对字节序列:

0xE60x880x910xE60x980xAF0xE40xB80xAD0xE50x9B0xBD0xE40xBA0xBA

按照UTF-8解码(new String(bsu,”UTF-8”),均可得到字符串”我是中国人”,但是如果我们对GB2312编码的字节序列用UTF-8解码,这就乱了套,所得的字符串明显是错误的乱码。

让我们来看一个试验。

import java.io.UnsupportedEncodingException;

public class u2g

{

         public static void main(String args[])throws UnsupportedEncodingException

         {

                   String str = args[0];

                   char chs[] = str.toCharArray();

                   System.out.println("Unicode characters:");

                   for(int i = 0; i < chs.length; i++)

                            System.out.print(chs[i] + " = " + (int)chs[i] + ";");

                   System.out.println();

                   String messages[] = {

                            "Encodes this String into a sequence of bytes using the" +

                            "\nplatform's default charset.",

                            "Encodes this String into a sequence of bytes using gb2312.",

                            "Encodes this String into a sequence of bytes using utf-8."};

                   String encodings[] = {null,"gb2312","utf-8"};

                   byte bs[][] = new byte[3][];

                   for(int h = 0; h < messages.length; h++){

                            System.out.print(messages[h]);

                            if(encodings[h] == null)bs[h] = str.getBytes();

                            else bs[h] = str.getBytes(encodings[h]);

                            for(int l = 0; l < bs[h].length; l++){

                                     if(l % 4 == 0)System.out.println();

                                     System.out.print("byte[" + l + "] = " +

                                                                            Integer.toHexString(bs[h][l] & 0xff) + ";");

                            }

                            System.out.println();

                   }

                   System.out.println("Decodes the sequence of bytes using corresponding encoding.");

                   for(int i = 0; i < bs.length; i++){

                            if(encodings[i] == null)System.out.println(new String(bs[i]));

                            else System.out.println(new String(bs[i], encodings[i]));

                   }

                   String messages1[] = {

                            "Decodes the sequence of bytes encoded by gb2312 into a string\nusing utf-8.",

                            "Decodes the sequence of bytes encoded by utf-8 into a string\nusing gb2312."};

                   for(int h = 0; h < 2; h ++){

                            System.out.println(messages1[h]);

                            str = new String(bs[h+1], encodings[h == 0 ? 2 : 1]);

                            chs = str.toCharArray();

                            System.out.print("Unicode characters:");

                            for(int i = 0; i < chs.length; i++)

                            {

                                     if(i % 4 == 0)System.out.println();

                                     System.out.print(chs[i] + " = " + (int)chs[i] + ";");

                            }

                            System.out.println();

                   }

                   System.out.println("The default encoding of system is " +

                                                           System.getProperty("file.encoding"));

         }

}

JVM输出如图 2-5所示,很明显,对用UTF-8编码的字节流,用GB2312编码是彻底失败了,我们什么字符也没得到。我所使用的系统是MS Windows 2000 Server,默认字符集是GBK,这个实验也可以看出GBK兼容GB2312。

本文地址:http://com.8s8s.com/it/it17113.htm