设为首页 加入收藏
 
首 页 开源技术和代码信息库 软硬件共享平台 软件技术文献库 呼叫中心
关键字:      热门搜索:JAVA  .NET  JSP  PHP  WEB开发  
用户登录
用户名:
密 码:
验证码: 验证码,看不清楚请用鼠标点击此处!
网站导航
推荐给好友
您的姓名
好友邮箱
   
 
当前位置:首页 > 软件技术文献 > 深入剖析JSP和Servlet对中文的处理过程(二)

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

 作者:执木  出处:赛迪网 发表时间: 2009-03-12 打印 下载 收藏 推荐给好友
jspc把JSP源文件转化为临时JAVA文件,并把字符串按照GB2312映射到Unicode,并用UTF格式写入JAVA文件中 jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中 .

  Servlet:从源文件到Class的过程

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

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

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

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

  注意,文中一共提到了三个变量:、和。其中,JSP文件只与有关,而和只与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("");
  out.println("#中文#");
  out.println("");
  }
  }
  该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。

  开始编译。下表是不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,不起任何作用。只对CLASS文件的输出产生影响,实际上是和一起,达到与JSP文件中的相同的效果,因为对编译和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)
  同ISO-8859-1
  同ISO-8859-1
  普通Java程序的编译过程与Servlet完全一样。

  CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?

  Class:输出字符串

  上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。

  看看上面的例子,如果给一串Unicode编码“00D6 00D0 00CE 00C4”,如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与“ISO8859-1”进行映射,则直接去掉前面的“00”即可得到“D6 D0 CE C4”,这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。

  各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,“D6 D0 CE C4”应该是我们所想要的,当把“D6 D0 CE C4”输出到IE中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。(当然了,如果你一定要用“西欧字符”来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为“00D6 00D0 00CE 00C4”本来就是由ISO8859-1转化过去的。

  给出如下结论:

  在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步“String.getBytes(???)”操作。???代表某一种字符集。

  如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的。

  如果是JSP,那么,这种内码就是在中指定的内码,也就是上文定义的。

  如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。

  当输出对象是浏览器时

  以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流“D6 D0 CE C4”,你可以尝试用各种内码去查看。你会发现用“简体中文”时能得到正确的结果。因为“D6 D0 CE C4”本来就是简体中文中“中文”两个字的编码。

  OK,完整地看一遍。  

JSP:源文件为GB2312格式的文本文件,且JSP源文件中有“中文”这两个汉字
  如果指定了为GB2312,转化过程如下表。
  表4 Jsp-charset = GB2312时的变化过程
  序号
  步骤说明
  结果
  1
  编写JSP源文件,且存为GB2312格式
  D6 D0 CE C4
  (D6D0=中 CEC4=文)
  2
  jspc把JSP源文件转化为临时JAVA文件,并把字符串按照GB2312映射到Unicode,并用UTF格式写入JAVA文件中
  E4 B8 AD E6 96 87
  3
  把临时JAVA文件编译成CLASS文件
  E4 B8 AD E6 96 87
  4
  运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
  4E 2D 65 87(在Unicode中4E2D=中 6587=文)
  5
  根据Jsp-charset=GB2312把Unicode转化为字节流
  D6 D0 CE C4
  6
  把字节流输出到IE中,并设置IE的编码为GB2312(作者按:这个信息隐藏在HTTP头中)
  D6 D0 CE C4
  7
  IE用“简体中文”查看结果
  “中文”(正确显示)
  如果指定了为ISO8859-1,转化过程如下表。
  表5 Jsp-charset = ISO8859-1时的变化过程
  序号
  步骤说明
  结果
  1
  编写JSP源文件,且存为GB2312格式
  D6 D0 CE C4
  (D6D0=中 CEC4=文)
  2
  jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中
  C3 96 C3 90 C3 8E C3 84
  3
  把临时JAVA文件编译成CLASS文件
  C3 96 C3 90 C3 8E C3 84
  4
  运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
  00 D6 00 D0 00 CE 00 C4
  (啥都不是!!!)
  5
  根据Jsp-charset=ISO8859-1把Unicode转化为字节流
  D6 D0 CE C4
  6
  把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)
  D6 D0 CE C4
  7
  IE用“西欧字符”查看结果
  乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样
  8
  改变IE的页面编码为“简体中文”

  “中文”(正确显示)

  奇怪了!为什么把设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互“抵消”的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。

  再看看不指定 时的情况。 

 表6 未指定Jsp-charset 时的变化过程
  序号
  步骤说明
  结果
  1
  编写JSP源文件,且存为GB2312格式
  D6 D0 CE C4
  (D6D0=中 CEC4=文)
  2
  jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中
  C3 96 C3 90 C3 8E C3 84
  3
  把临时JAVA文件编译成CLASS文件
  C3 96 C3 90 C3 8E C3 84
  4
  运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
  00 D6 00 D0 00 CE 00 C4
  (啥都不是!!!)
  5
  根据Jsp-charset=ISO8859-1把Unicode转化为字节流
  D6 D0 CE C4
  6
  把字节流输出到IE中
  D6 D0 CE C4
  7
  IE用发出请求时的页面的编码查看结果
  视情况而定。如果是简体中文,则能正确显示,否则,需执行表5中的第8步
  Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有“中文”这两个汉字
  如果=GB2312,=GB2312
  表7 Compile-charset=Servlet-charset=GB2312 时的变化过程
  序号
  步骤说明
  结果
  1
  编写Servlet源文件,且存为GB2312格式
  D6 D0 CE C4
  (D6D0=中 CEC4=文)
  2
  用javac –encoding GB2312把JAVA源文件编译成CLASS文件
  E4 B8 AD E6 96 87 (UTF)
  3
  运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
  4E 2D 65 87 (Unicode)
  4
  根据Servlet-charset=GB2312把Unicode转化为字节流
  D6 D0 CE C4 (GB2312)
  5
  把字节流输出到IE中并设置IE的编码属性为Servlet-charset=GB2312
  D6 D0 CE C4 (GB2312)
  6
  IE用“简体中文”查看结果
  “中文”(正确显示)
  如果=ISO8859-1,=ISO8859-1
  表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程
  序号
  步骤说明

  结果  


1
  编写Servlet源文件,且存为GB2312格式
  D6 D0 CE C4
  (D6D0=中 CEC4=文)
  2
  用javac –encoding ISO8859-1把JAVA源文件编译成CLASS文件
  C3 96 C3 90 C3 8E C3 84 (UTF)
  3
  运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
  00 D6 00 D0 00 CE 00 C4
  (啥都不是!!!)
  4
  根据Servlet-charset=ISO8859-1把Unicode转化为字节流
  D6 D0 CE C4
  5
  把字节流输出到IE中并设置IE的编码属性为Servlet-charset=ISO8859-1
  D6 D0 CE C4 (GB2312)
  6
  IE用“西欧字符”查看结果
  乱码(原因同表5)
  7
  改变IE的页面编码为“简体中文”
  “中文”(正确显示)

  

如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。

  当Compile-charset=Servlet-charset时,第2步和第4步能互逆,“抵消”,显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。

  当输出对象是数据库时

  输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。

  假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。

  表9 输出对象是数据库时的变化过程(1)  

序号
  步骤说明
  结果
  域
  1
  在IE中输入“中文”
  D6 D0 CE C4
  IE
  2
  IE把字符串转变成UTF,并送入传输流中
  E4 B8 AD E6 96 87
  3
  Servlet接收到输入流,用readUTF读取
  4E 2D 65 87(unicode)
  Servlet
  4
  编程者在Servlet中必须把字符串根据GB2312还原为字节流
  D6 D0 CE C4
  5
  编程者根据数据库内码ISO8859-1生成新的字符串
  00 D6 00 D0 00 CE 00 C4
  6
  把新生成的字符串提交给JDBC
  00 D6 00 D0 00 CE 00 C4
  7
  JDBC检测到数据库内码为ISO8859-1
  00 D6 00 D0 00 CE 00 C4
  JDBC
  8
  JDBC把接收到的字符串按照ISO8859-1生成字节流
  D6 D0 CE C4
  9
  JDBC把字节流写入数据库中
  D6 D0 CE C4
  10
  完成数据存储工作
  D6 D0 CE C4 数据库
  以下是从数据库中取出数的过程
  11
  JDBC从数据库中取出字节流
  D6 D0 CE C4
  JDBC
  12
  JDBC按照数据库的字符集ISO8859-1生成字符串,并提交给Servlet
  00 D6 00 D0 00 CE 00 C4 (Unicode)
  13
  Servlet获得字符串
  00 D6 00 D0 00 CE 00 C4 (Unicode)
  Servlet
  15
  编程者必须根据数据库的内码ISO8859-1还原成原始字节流
  D6 D0 CE C4
  16
  编程者必须根据客户端字符集GB2312生成新的字符串
  4E 2D 65 87
  (Unicode)
  Servlet准备把字符串输出到客户端
  17
  Servlet根据生成字节流
  D6 D0 CE C4
  Servlet
  18
  Servlet把字节流输出到IE中,如果已指定,还会设置IE的编码为
  D6 D0 CE C4
  19
  IE根据指定的编码或默认编码查看结果
  “中文”(正确显示)
  IE

  解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:“new String(source.getBytes("GB2312"), "ISO8859-1")”。第15、16两步也是一句话:“new String(source.getBytes("ISO8859-1"), "GB2312")”。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?

  至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。

  行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。

  因为我们早就被告之要这么做了。

  以下给出一个结论,作为结尾。

  1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是“字符串变量是基于字符集的”;

  2、 在Servlet中,必须用HttpServletResponse.setContentType()设置charset,且设置成与客户端内码一致;对于其中的字符串常量,需要在Javac编译时指定encoding,这个encoding必须与编写源文件的平台的字符集一样,一般说来都是GB2312或GBK;对于字符串变量,与JSP一样,必须“是基于字符集的”。

对本文的评价
太差! (1)
需提高 (2)
一般 (3)
好文章 (4)
真棒!(5)
评论
发表评论人笔名:
其他用户评论
暂无评论
合肥服务外包公共服务平台网站群:合肥服务外包网   公共信息平台  公共技术平台   公共培训平台
合肥市外经贸局地址:合肥市政务新区B座9-10层 邮编:230022 监督电话:3538679 传真:3538677
Email:hffetc@mail.hf.ah.cn 合肥市外经贸局行政服务中心窗口电话:3537035、3537377
版权所有:合肥市对外贸易经济合作局 皖ICP备06004470号