java程序员都曾遇到过这样的问题:输入的中文不能正确显示在界面上,保存在数据库中的也是一堆乱码,或者数据库或数据文件中存放的是正确的中文,可是在java程序中看到的却是一大串的“?”。
这就是通常所说的“中文问题”。
java中与中文相关的编码
在jdk中,提供了对大多数常用语言的支持。在解决“中文问题”时,表1中的编码是最常用,或者就是最有关系的。
表1 jdk中与中文相关的编码列表
在实际编程时,接触得比较多的是gb2312(gbk)和iso8859-1。
注:utf是unicode transformation format的缩写,意为unicode转换格式。可以这么描述java程序中unicode与utf的关系,虽然不绝对。字符串在内存中运行时,表现为unicode代码,而当要保存到文件或其它介质中去时,用的是utf。这个转化过程是由writeutf和readutf来完成得。
servlet/jsp对中文的处理过程
总体流程
把问题想成是一个黑匣子。先看黑匣子的一级表示(如图1所示):
图1 ipo模型
这就是一个ipo模型,即输入、处理和输出。同样的内容要经过“从charseta到unicode再到charsetb”的转化。
再看二级表示(如图2所示):
图2 jsp、java输出模型
在这个图中,输入的是jsp和java源文件。在处理过程中,以class文件为载体,然后输出。再细化到三级(如图3所示):
图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=
2. jspc用相当于“javac -encoding
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”。
如果上述代码中不指定
到现在为止,已经解释了从jsp文件到class文件的转变过程中中文字符的映射过程。一句话,从“jsp-charset到unicode再到utf”。表2总结了这个过程:
表2 “中文”从jsp到class的转化过程
servlet:从源文件到class的过程
servlet源文件是以“.java”结尾的文本文件。我们将讨论servlet的编译过程并跟踪其中的中文变化。
用“javac”编译servlet源文件。javac可以带“-encoding
源文件在编译时,用
在servlet中,还有一个地方设置输出流的charset。通常在输出结果前,调用httpservletresponse的setcontent type方法来达到与在jsp中设置
注意:文中一共提到了三个变量:
看下例:
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是
表3 “中文”从servlet源文件到class的转变过程
注意:普通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()方法中指定的,也就是上文定义的
闽公网安备 35060202000074号