`
hilor
  • 浏览: 27142 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
最近访客 更多访客>>
社区版块
存档分类
最新评论

深入剖析JSP和Servlet对中文的处理过程(转)

    博客分类:
  • J2EE
阅读更多

概述

 

世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。

这是一个世界范围内都存在的问题,所以, Java 提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。

汉字是双字节的。所谓双字节是指一个双字要占用两个 BYTE 的位置(即 16 位),分别称为高位和低位。中国规定的汉字编码为 GB2312 ,这是强制性的,目前几乎所有的能处理中文的应用程序都支持 GB2312 GB2312 包括了一二级汉字和 9 区符号,高位从 0xa1 0xfe ,低位也是从 0xa1 0xfe ,其中,汉字的编码范围为 0xb0a1 0xf7fe

另外有一种编码,叫做 GBK ,但这是一份规范,不是强制的。 GBK 提供了 20902 个汉字,它兼容 GB2312 ,编码范围为 0x8140 0xfefe GBK 中的所有字符都可以一一映射到 Unicode 2.0

在不久的将来,中国会颁布另一种标准: GB18030-2000 GBK2K )。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与 GBK 兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从 0x81 0xfe ,二字节和第四字节从 0x30 0x39

本文不打算介绍 Unicode ,有兴趣的可以浏览“ http://www.unicode.org/ ”查看更多的信息。 Unicode 有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与 Unicode 的映射关系,而 Java 正是利用了这一点以达到异种语言之间的转换。

JDK 中,与中文相关的编码有:

1 JDK中与中文相关的编码列表

编码名称

说    明

ASCII

7 位,与 ascii7 相同

ISO8859-1

8- 位,与 8859_1,ISO-8859-1,ISO_8859-1,latin1... 等相同

GB2312-80

16 位,与 gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB... 等相同

GBK

MS936 相同,注意:区分大小写

UTF8

UTF-8 相同

GB18030

cp1392 1392 相同,目前支持的 JDK 很少

在实际编程时,接触得比较多的是 GB2312 GBK )和 ISO8859-1

 

为什么会有“ ? ”号

 

上文说过,异种语言之间的转换是通过 Unicode 来完成的。假设有两种不同的语言 A B ,转换的步骤为:先把 A 转化为 Unicode ,再把 Unicode 转化为 B

举例说明。有 GB2312 中有一个汉字“李”,其编码为“ C0EE ”,欲转化为 ISO8859-1 编码。步骤为:先把“李”字转化为 Unicode ,得到“ 674E ”,再把“ 674E ”转化为 ISO8859-1 字符。当然,这个映射不会成功,因为 ISO8859-1 中根本就没有与“ 674E ”对应的字符。

当映射不成功时,问题就发生了!当从某语言向 Unicode 转化时,如果在某语言中没有该字符,得到的将是 Unicode 的代码“ \uffffd ”(“ \u ”表示是 Unicode 编码,)。而从 Unicode 向某语言转化时,如果某语言没有对应的字符,则得到的是“ 0x3f ”(“ ? ”)。这就是“ ? ”的由来。

例如:把字符流 buf = 0x80 0x40 0xb0 0xa1 ”进行 new String(buf, "gb2312") 操作,得到的结果是“ \ufffd\u554a ”,再 println 出来,得到的结果将是“ ? 啊”,因为“ 0x80 0x40 ”是 GBK 中的字符,在 GB2312 中没有。

再如,把字符串 String="\u00d6\u00ec\u00e9\u0046\u00bb\u00f9" 进行 new String (buf.getBytes("GBK")) 操作,得到的结果是“ 3fa8aca8a6463fa8b4 ”,其中,“ \u00d6 ”在“ GBK ”中没有对应的字符,得到“ 3f ”,“ \u00ec ”对应着“ a8ac ”,“ \u00e9 ”对应着“ a8a6 ”,“ 0046 ”对应着“ 46 ”(因为这是 ASCII 字符),“ \u00bb ”没找到,得到“ 3f ”,最后,“ \u00f9 ”对应着“ a8b4 ”。把这个字符串 println 一下,得到的结果是“ ?ìéF?ù ”。看到没?这里并不全是问号,因为 GBK Unicode 映射的内容中除了汉字外还有字符,本例就是最好的明证。

所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了, 50 步和 100 步并没有质的差别。

或者会问:如果源字符集中有,而 Unicode 中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在 Java 中,如果发生这种情况,是会抛出异常的。

 

什么是 UTF

 

UTF ,是 Unicode Text Format 的缩写,意为 Unicode 文本格式。对于 UTF ,是这样定义的:

1 )如果 Unicode 16 位字符的头 9 位是 0 ,则用一个字节表示,这个字节的首位是“ 0 ”,剩下的 7 位与原字符中的后 7 位相同,如“ \u0034 ”( 0000 0000 0011 0100 ),用“ 34 (0011 0100) 表示;(与源 Unicode 字符是相同的);

2 )如果 Unicode 16 位字符的头 5 位是 0 ,则用 2 个字节表示,首字节是“ 110 ”开头,后面的 5 位与源字符中除去头 5 个零后的最高 5 位相同;第二个字节以“ 10 ”开头,后面的 6 位与源字符中的低 6 位相同。如“ \u025d ”( 0000 0010 0101 1101 ),转化后为“ c99d ”( 1100 1001 1001 1101 );

3 )如果不符合上述两个规则,则用三个字节表示。第一个字节以“ 1110 ”开头,后四位为源字符的高四位;第二个字节以“ 10 ”开头,后六位为源字符中间的六位;第三个字节以“ 10 ”开头,后六位为源字符的低六位;如“ \u9da7 ”( 1001 1101 1010 0111 ),转化为“ e9b6a7 ”( 1110 1001 1011 0110 1010 0111 );

可以这么描述 JAVA 程序中 Unicode UTF 的关系,虽然不绝对:字符串在内存中运行时,表现为 Unicode 代码,而当要保存到文件或其它介质中去时,用的是 UTF 。这个转化过程是由 writeUTF readUTF 来完成的。

 

好了,基础性的论述差不多了,下面进入正题。

 

先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:

 input(charsetA)->process(Unicode)->output(charsetB)

简单,这就是一个 IPO 模型,即输入、处理和输出。同样的内容要经过“从 charsetA unicode 再到 charsetB ”的转化。

再看二级表示:

   SourceFile(jsp,java)->class->output

 

在这个图中,可以看出,输入的是 jsp java 源文件,在处理过程中,以 Class 文件为载体,然后输出。再细化到三级表示:

jsp->temp file->class->browser,os console,db

app,servlet->class->browser,os console,db

 

这个图就更明白了。 Jsp 文件先生成中间的 Java 文件,再生成 Class 。而 Servlet 和普通 App 则直接编译生成 Class 。然后,从 Class 再输出到浏览器、控制台或数据库等。

 

JSP :从源文件到 Class 的过程

 

Jsp 的源文件是以“ .jsp ”结尾的文本文件。在本节中,将阐述 JSP 文件的解释和编译过程,并跟踪其中的中文变化。

1 JSP/Servlet 引擎提供的 JSP 转换工具( jspc )搜索 JSP 文件中用 <%@ page contentType ="text/html; charset=<Jsp-charset>" %> 中指定的 charset 。如果在 JSP 文件中未指定 <Jsp-charset > ,则取 JVM 中的默认设置 file.encoding ,一般情况下,这个值是 ISO8859-1

2 jspc 用相当于“ javac –encoding <Jsp-charset > ”的命令解释 JSP 文件中出现的所有字符,包括中文字符和 ASCII 字符,然后把这些字符转换成 Unicode 字符,再转化成 UTF 格式,存为 JAVA 文件。 ASCII 码字符转化为 Unicode 字符时只是简单地在前面加“ 00 ”,如“ A ”,转化为“ \u0041 ”(不需要理由, Unicode 的码表就是这么编的)。然后,经过到 UTF 的转换,又变回“ 41 ”了!这也就是可以使用普通文本编辑器查看由 JSP 生成的 JAVA 文件的原因;

3 、引擎用相当于“ javac –encoding UNICODE ”的命令,把 JAVA 文件编译成 CLASS 文件;

先看一下这些过程中中文字符的转换情况。有如下源代码:

<%@ page contentType="text/html; charset=gb2312"%>

<html><body>

<%

  String a=" 中文 ";

  out.println(a);

%>

</body></html>

这段代码是在 UltraEdit for Windows 上编写的。保存后,“中文”两个字的 16 进制编码为“ D6 D0 CE C4 ”( GB2312 编码)。经查表,“中文”两字的 Unicode 编码为“ \u4E2D\u6587 ”,用 UTF 表示就是“ E4 B8 AD E6 96 87 ”。打开引擎生成的由 JSP 文件转变而成的 JAVA 文件,发现其中的“中文”两个字确实被“ E4 B8 AD E6 96 87 ”替代了,再查看由 JAVA 文件编译生成的 CLASS 文件,发现结果与 JAVA 文件中的完全一样。

再看 JSP 中指定的 CharSet ISO-8859-1 的情况。

<%@ page contentType="text/html; charset=ISO-8859-1"%>

<html><body>

<%

  String a=" 中文 ";

  out.println(a);

%>

</body></html>

同样,该文件是用 UltraEdit 编写的,“中文”这两个字也是存为 GB2312 编码“ D6 D0 CE C4 ”。先模拟一下生成的 JAVA 文件和 CLASS 文件的过程: jspc ISO-8859-1 来解释“中文”,并把它映射到 Unicode 。由于 ISO-8859-1 8 位的,且是拉丁语系,其映射规则就是在每个字节前加“ 00 ”,所以,映射后的 Unicode 编码应为“ \u00D6\u00D0\u00CE\u00C4 ”,转化成 UTF 后应该是“ C3 96 C3 90 C3 8E C3 84 ”。好,打开文件看一下, JAVA 文件和 CLASS 文件中,“中文”果然都表示为“ C3 96 C3 90 C3 8E C3 84 ”。

如果上述代码中不指定 <Jsp-charset > ,即把第一行写成“ <%@ page contentType="text/html" %> ”, JSPC 会使用 file.encoding 的设置来解释 JSP 文件。在 RedHat 6.2 上,其处理结果与指定为 ISO-8859-1 是完全相同的。

到现在为止,已经解释了从 JSP 文件到 CLASS 文件的转变过程中中文字符的映射过程。一句话:从“ JspCharSet Unicode 再到 UTF ”。下表总结了这个过程:

2 “中文”从JSP到CLASS的转化过程

Jsp-CharSet

JSP 文件中

JAVA 文件中

CLASS 文件中

GB2312

D6 D0 CE C4

(GB2312)

\u4E2D\u6587(Unicode)

E4 B8 AD E6 96 87 (UTF)

E4 B8 AD E6 96 87 (UTF)

ISO-8859-1

D6 D0 CE C4

(GB2312)

\u00D6\u00D0\u00CE\u00C4 (Unicode) C3 96 C3 90 C3 8E C3 84 (UTF)

C3 96 C3 90 C3 8E C3 84 (UTF)

无(默认= file.encoding

ISO-8859-1

ISO-8859-1

ISO-8859-1

 

下节先讨论 Servlet JAVA 文件到 CLASS 文件的转化过程,然后再解释从 CLASS 文件如何输出到客户端。之所以这样安排,是因为 JSP Servlet 在输出时处理方法是一样的。

 

Servlet :从源文件到 Class 的过程

 

Servlet 源文件是以“ .java ”结尾的文本文件。本节将讨论 Servlet 的编译过程并跟踪其中的中文变化。

用“ javac ”编译 Servlet 源文件。 javac 可以带“ -encoding <Compile-charset > ”参数,意思是“用 < Compile-charset > 中指定的编码来解释 Serlvet 源文件”。

源文件在编译时,用 <Compile-charset > 来解释所有字符,包括中文字符和 ASCII 字符。然后把字符常量转变成 Unicode 字符,最后,把 Unicode 转变成 UTF

Servlet 中,还有一个地方设置输出流的 CharSet 。通常在输出结果前,调用 HttpServletResponse setContentType 方法来达到与在 JSP 中设置 <Jsp-charset > 一样的效果,称之为 <Servlet-charset >

注意,文中一共提到了三个变量: <Jsp-charset > <Compile-charset > <Servlet-charset > 。其中, JSP 文件只与 <Jsp-charset > 有关,而 <Compile-charset > <Servlet-charset > 只与 Servlet 有关。

看下例:

import javax.servlet.*;

import javax.servlet.http.*;

 

class testServlet extends HttpServlet

{

       public void doGet(HttpServletRequest req,HttpServletResponse resp)

       throws ServletException,java.io.IOException

       {

              resp.setContentType("text/html; charset=GB2312");

              java.io.PrintWriter out=resp.getWriter();

              out.println("<html>");

              out.println("# 中文 # ");

              out.println("</html>");

       }

}

该文件也是用 UltraEdit for Windows 编写的,其中的“中文”两个字保存为“ D6 D0 CE C4 ”( GB2312 编码)。

开始编译。下表是 <Compile-charset > 不同时, CLASS 文件中“中文”两字的十六进制码。在编译过程中, <Servlet-charset > 不起任何作用。 <Servlet-charset > 只对 CLASS 文件的输出产生影响,实际上是 <Servlet-charset > <Compile-charset > 一起,达到与 JSP 文件中的 <Jsp-charset > 相同的效果,因为 <Jsp-charset > 对编译和 CLASS 文件的输出都会产生影响。

3 “中文”从Servlet源文件到Class的转变过程

Compile-charset

Servlet 源文件中

Class 文件中

等效的 Unicode

GB2312

D6 D0 CE C4

(GB2312)

E4 B8 AD E6 96 87 (UTF)

\u4E2D\u6587 ( Unicode 中=“中文” )

ISO-8859-1

D6 D0 CE C4

(GB2312)

C3 96 C3 90 C3 8E C3 84 (UTF)

\u00D6 \u00D0 \u00CE \u00C4 ( D6 D0 CE C4 前面各加了一个 00)

无(默认)

D6 D0 CE C4

(GB2312)

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics