服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

剖析jsp和servlet对中文的处理(1)


  java程序员都曾遇到过这样的问题:输入的中文不能正确显示在界面上,保存在数据库中的也是一堆乱码,或者数据库或数据文件中存放的是正确的中文,可是在java程序中看到的却是一大串的“?”。
  这就是通常所说的“中文问题”。
  java中与中文相关的编码
  在jdk中,提供了对大多数常用语言的支持。在解决“中文问题”时,表1中的编码是最常用,或者就是最有关系的。
  表1 jdk中与中文相关的编码列表
   剖析jsp和servlet对中文的处理(1)(图一)
  在实际编程时,接触得比较多的是gb2312(gbk)和iso8859-1。
  注:utf是unicode transformation format的缩写,意为unicode转换格式。可以这么描述java程序中unicode与utf的关系,虽然不绝对。字符串在内存中运行时,表现为unicode代码,而当要保存到文件或其它介质中去时,用的是utf。这个转化过程是由writeutf和readutf来完成得。
  servlet/jsp对中文的处理过程
  总体流程
  把问题想成是一个黑匣子。先看黑匣子的一级表示(如图1所示):
   剖析jsp和servlet对中文的处理(1)(图二)
  图1 ipo模型
  这就是一个ipo模型,即输入、处理和输出。同样的内容要经过“从charseta到unicode再到charsetb”的转化。
  再看二级表示(如图2所示):
   剖析jsp和servlet对中文的处理(1)(图三)
  图2 jsp、java输出模型
  在这个图中,输入的是jsp和java源文件。在处理过程中,以class文件为载体,然后输出。再细化到三级(如图3所示):
   剖析jsp和servlet对中文的处理(1)(图四)
  图3 ipo模型
  jsp文件先生成中间的java文件,再生成class。而servlet和普通app则直接编译生成class,然后,从class再输出到浏览器、控制台或数据库等。
  jsp:从源文件到class的过程
  
  jsp源文件是以“.jsp”结尾的文本文件。在本节中,将阐述jsp文件的解释和编译过程,并跟踪其中中文内容的变化。
  
  一般地,jsp源文件经过如下步骤后变成可被引擎执行的class文件:
  
  1. jsp/servlet引擎提供的jsp转换工具(jspc)搜索jsp文件中用<%@ page contenttype ="text/html; charset="%>中指定的charset。如果在jsp文件中未指定,则默认为iso8859-1(或者说是latin-1)。
  
  2. jspc用相当于“javac -encoding ”解释jsp文件中出现的所有字符,包括中文字符和ascii字符。然后把这些字符转换成unicode字符,再转化成utf格式,存为java文件。ascii码字符转化为unicode字符时只是简单地在前面加“00”,如“a”,转化为“/u0041”。然后,经过了utf的转换,又变回“41”了。这也就是可以使用普通文本编辑器查看由jsp生成的java文件的原因。
  
  3. 引擎用相当于“javac -encoding utf-8”的命令,把java文件编译成class文件。
  
  先看一下这些过程中中文字符的转换情况。有如下源代码:
  
  <%@ page contenttype="text/html; charset=gb2312"%>
  
  <%
  string a="中文";
  out.println(a);
  %>
  
  这段代码是在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文件中的完全一样,也是“e4 b8 ad e6 96 87”。
  再看jsp中指定的charset为iso-8859-1的情况:
  <%@ page contenttype="text/html; charset=iso-8859-1"%>
  
  <%
  string a="中文";
  out.println(a);
  %>
  
  同样,该文件是用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”。
  如果上述代码中不指定,即把第一行写成“<%@ page contenttype="text/html" %>”,jspc会使用默认的“iso8859-1”来解释jsp文件。
  到现在为止,已经解释了从jsp文件到class文件的转变过程中中文字符的映射过程。一句话,从“jsp-charset到unicode再到utf”。表2总结了这个过程:
  表2 “中文”从jsp到class的转化过程
   剖析jsp和servlet对中文的处理(1)(图五)
  servlet:从源文件到class的过程
  
  servlet源文件是以“.java”结尾的文本文件。我们将讨论servlet的编译过程并跟踪其中的中文变化。
  
  用“javac”编译servlet源文件。javac可以带“-encoding ”参数,意思是“用< compile-charset >中指定的编码来解释serlvet源文件”。
  源文件在编译时,用来解释所有字符,包括中文字符和ascii字符。然后把字符常量转变成unicode字符。最后,把unicode转变成utf。
  在servlet中,还有一个地方设置输出流的charset。通常在输出结果前,调用httpservletresponse的setcontent type方法来达到与在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编码)。
  开始编译。表3是不同时,class文件中“中文”两字的十六进制码。在编译过程中,不起任何作用。只对class文件的输出产生影响,可以说一起,达到与jsp文件中的相同的效果,因为对编译过程和class文件的输出都会产生影响。
  表3 “中文”从servlet源文件到class的转变过程
   剖析jsp和servlet对中文的处理(1)(图六)
  注意:普通java程序的编译过程与servlet完全一样。
  截止现在,从jsp或servlet的源文件到class文件的过程中中文内容的蜕变历程是不是昭然若揭了?ok,接下来看看class文件中的中文又是怎样被输出的呢?
  
  class:输出字符串
  
  class文件是java程序的一种存储载体。当class文件被虚拟机执行时,通过readutf把class文件中的内容读入内存中。字符串在内存中表示为unicode编码。当要把内存中的内容输出到别的程序或是外围设备(如终端)上去时,问题就来了(为了简单起见,把“别的程序或外围设备”称之为“输出对象”)。
  
  1.如果输出对象能处理unicode字符,则一切都很简单,只要把unicode字符直接传给输出对象即可。
  
  2.事实是,大多数输出对象不能直接处理unicode,它们只能处理iso8859-1和gb2312等。在往输出对象输出字符串时,需要做一定的转换才行。
  
  看看下面的例子,给定一个有四个字符的unicode字符串“00d6 00d0 00ce 00c4”,如果输出到只能识别“iso8859-1”的程序中去,则直接去掉前面的“00”即可得到目的字符串“d6 d0 ce c4”。假如把它们输出到gb2312的程序中去,得到的结果很可能是一大堆乱码。因为在gb2312中可能没有(也有可能有)字符与00d6等字符对应(如果对应不上,将得到0x3f,也就是问号,如果对应上了,由于00d6等字符太靠前,估计也是一些特殊符号,真正的汉字在unicode中的编码从4e00开始)。
  
  同样的unicode字符,输出到不同编码的对象中去时,结果是不同的。当然,这其中有一种是我们期望的结果。对于能处理中文的输出对象而言,自然希望输入的内容(也就是java程序输出的内容)是基于gb2312编码有意义的中文字符串。
  
  以上例而论,“d6 d0 ce c4”应该是我们所想要的。当把“d6 d0 ce c4”输出到ie中时,用“简体中文”方式查看,就能看到清楚的“中文”两个字了。
  
  得出如下结论:
  
  java程序在输出字符串前,必须先把unicode的字符串按照某一种内码重新生成字节流,然后把字节流输出给“输出对象”,相当于进行了一步“string.getbytes(???)”操作,其中???代表一种字符集的名字。
  
  1.如果是servlet,这种字符集是在httpservlet response.setcontenttype()方法中指定的,也就是上文定义的

扫描关注微信公众号