因为一直不信java竟会有不能混排显示多国语言的bug,这个周末研究了一下servlet、
jsp的多国语言显示的问题,也就是servlet的多字符集问题,由于我对字符集的概念还
不是很清晰所以写出的东西未必是准确的,我是这样理解java中的字符集的:在运行时
,每个字符串对象中存储的都是编码为unicode内码的(我觉得所有的语言中都是有相应
编码的,因为在计算机内部字符串总是用内码来表示的,只不过一般计算机语言中的字
符串编码时平台相关的,而java则采用了平台无关的unicode)。
java从一个byte流中读取一个字符串时,将把平台相关的byte转变为平台无关的un
icode字符串。在输出时java将把unicode字符串转变为平台相关的byte流,如果某个un
icode字符在某个平台上不存在,将会输出一个'?'。举个例子:在中文windows中,jav
a读出一个"gb2312"编码的文件(可以是任何流)到内存中构造字符串对象,将会把gb2
312编码的文字转变为unicode编码的字符串,如果把这个字符串输出又将会把unicode字
符串转化为gb2312的byte流或数组:"中文测试"----->"\u4e2d\u6587\u6d4b\u8bd5"--
--->"中文测试"。
如下例程:
byte[] bytes = new byte[]{(byte)0xd6, (byte)0xd0, (byte)0xce, (byte)0xc4, (b
yte)0xb2, (byte)0xe2, (byte)0xca, (byte)0xd4};//gbk编码的"中文测试"
java.io.bytearrayinputstream bin = new java.io.bytearrayinputstream(bytes);
java.io.bufferedreader reader = new java.io.bufferedreader(new java.io. inpu
tstreamreader (bin,"gbk"));
string msg = reader.readline();
system.out.println(msg)
这段程序放到包含"中文测试"这四个字的系统(如中文系统)中,可以正确地打印
出这些字。msg字符串中包含了正确的"中文测试"的unicode编码:"\u4e2d\u6587\u6d4
b\u8bd5",打印时转换为操作系统的默认字符集,是否可以正确显示依赖于操作系统的
字符集,只有在支持相应字符集的系统中,我们的信息才能正确的输出,否则得到的将
会是垃圾。
话入正题,我们来看看servlet/jsp中的多语言问题。我们的目标是,任一国家的客
户端通过form向server发送信息,server把信息存入数据库中,客户端在检索时仍然能
够看到自己发送的正确信息。事实上,我们要保证,最终server中的sql语句中保存的时
包含客户端发送文字的正确unicode编码;dbc与数据库通讯时采用的编码方式能包含客
户端发送的文字信息,事实上,最好让jdbc直接使用unicode/utf8与数据库通讯!这样
就可以确保不会丢失信息;server向客户端发送的信息时也要采用不丢失信息的编码方
式,也可以是unicode/utf8。
如果不指定form的enctype属性,form将把输入的内容依照当前页面的编码字符集u
rlencode之后再提交,服务器端得到是urlencoding的字符串。编码后得到的urlencodi
ng字符串是与页面的编码相关的,如gb2312编码的页面提交"中文测试",得到的是"%d6
%d0%ce%c4%b2%e2%ca%d4",每个"%"后跟的是16进制的字符串;而在utf8编码时得到的
却是"%e4%b8%ad%e6%96%87%e6%b5%8b%e8%af%95",因为gb2312编码中一个汉字是16位的
,而utf8中一个汉字却是24位的。中日韩三国的ie4以上浏览器均支持utf8编码,这种方
案肯定包涵了这三国语言,所以我们如果让html页面使用utf8编码那么将至少可以支持
这三国语言。
但是,如果我们html/jsp页面使用utf8编码,因为应用程序服务器可能不知道这种
情况,因为如果浏览器发送的信息不包含charset信息,至多server知道读到accept-la
nguage请求投标,我们知道仅靠这个投标是不能获知浏览器所采用编码的,所以应用程
序服务器不能正确解析提交的内容,为什么?因为java中的所有字符串都是unicode16位
编码的,httpservletrequest.request(string)的功能就是把客户端提交的urlencode编
码的信息转为unicode字符串,有些server只能认为客户端的编码和server平台相同,简
单地使用urldecoder.decode(string)方法直接解码,如果客户端编码恰好和server相同
,那么就可以得到正确地字符串,否则,如果提交地字符串中包含了当地字符,那么将
会导致垃圾信息。
在我提出的这个解决方案里,已经指定了采用utf8编码,所以,可以避免这个问题
,我们可以自己定制出decode方法:
public static string decode(string s,string encoding) throws exception {
stringbuffer sb = new stringbuffer();
for(int i=0; i<s.length(); i++) {
char c = s.charat(i);
switch (c) {
case '+':
sb.append(' ');
break;
case '%':
try {
sb.append((char)integer.parseint(
s.substring(i+1,i+3),16));
}
catch (numberformatexception e) {
throw new illegalargumentexception();
}
i += 2;
break;
default:
sb.append(c);
break;
}
}
// undo conversion to external encoding
string result = sb.tostring();
byte[] inputbytes = result.getbytes("8859_1");
return new string(inputbytes,encoding);
}
这个方法可以指定encoding,如果把它指定为utf8就满足了我们的需要。比如用它
解析:"%e4%b8%ad%e6%96%87%e6%b5%8b%e8%af%95"就可以得到正确的汉字"中文测试"的
unicode字符串。
现在的问题就是我们必须得到客户端提交的urlencode的字符串。对于method为get的fo
rm提交的信息,可以用httpservletrequest.getquerystring()方法读到,而对于post方
法的form提交的信息,只能从servletinputstream中读到,事实上标准的getparameter
方法被第一次调用后,form提交的信息就被读取出来了,而servletinputstream是不能
重复读出的。所以我们应在第一次使用getparameter方法前读取并解析form提交的信息
。
我是这么做的,建立一个servlet基类,覆盖service方法,在调用父类的service方
法前读取并解析form提交的内容,请看下面的源代码:
package com.hto.servlet;
import javax.servlet.http.httpservletrequest;
import java.util.*;
/**
* insert the type's description here.
* creation date: (2001-2-4 15:43:46)
* @author: 钱卫春
*/
public class utf8parameterreader {
hashtable pairs = new hashtable();
/**
* utf8parameterreader constructor comment.
*/
public utf8parameterreader(httpservletrequest request) throws java.io.ioexce
ption{
super();
parse(request.getquerystring());
parse(request.getreader().readline());
}
/**
* utf8parameterreader constructor comment.
*/
public utf8parameterreader(httpservletrequest request,string encoding) throw
s java
public static string decode(string s) throws exception {
stringbuffer sb = new stringbuffer();
for(int i=0; i<s.length(); i++) {
char c = s.charat(i);
switch (c) {
case '+':
sb.append(' ');
break;
case '%':
try {
sb.append((char)integer.parseint(
s.substring(i+1,i+3),16));
}
catch (numberformatexception e) {
throw new illegalargumentexception();
}
i += 2;
break;
default:
sb.append(c);
break;
}
}
// undo conversion to external encoding
string result = sb.tostring();
byte[] inputbytes = result.getbytes("8859_1");
return new string(inputbytes,"utf8");
}
public static string decode(string s,string encoding) throws exception {
stringbuffer sb = new stringbuffer();
for(int i=0; i<s.length(); i++) {
char c = s.charat(i);
switch (c) {
case '+':
sb.append(' ');
break;
case '%':
try {
sb.append((char)integer.parseint(
s.substring(i+1,i+3),16));
}
catch (numberformatexception e) {
throw new illegalargumentexception();
}
i += 2;
break;
default:
sb.append(c);
break;
}
}
// undo conversion to external encoding
string result = sb.tostring();
byte[] inputbytes = result.getbytes("8859_1");
return new string(inputbytes,encoding);
}
/**
* insert the method's description here.
* creation date: (2001-2-4 17:30:59)
* @return java.lang.string
* @param name java.lang.string
*/
public string getparameter(string name) {
if (pairs == null !pairs.containskey(name)) return null;
return (string)(((arraylist) pairs.get(name)).get(0));
}
/**
* insert the method's description here.
* creation date: (2001-2-4 17:28:17)
* @return java.util.enumeration
*/
public enumeration getparameternames() {
if (pairs == null) return null;
return pairs.keys();
}
/**
* insert the method's description here.
* creation date: (2001-2-4 17:33:40)
* @return java.lang.string[]
* @param name java.lang.string
*/
public string[] getparametervalues(string name) {
if (pairs == null !pairs.containskey(name)) return null;
arraylist al = (arraylist) pairs.get(name);
string[] values = new string[al.size()];
for(int i=0;i<values.length;i++)
values[i] = (string) al.get(i);
return values;
}
/**
* insert the method's description here.
* creation date: (2001-2-4 20:34:37)
* @param urlenc java.lang.string
*/
private void parse(string urlenc) throws javaelse{
name = apair;
value = "";
}
if(pairselse{
arraylist values = new arraylist();
values.add(value);
pairs.put(name,values);
}
}
}catch(exception e){
throw new java.io.ioexception(e.getmessage());
}
}
/**
* insert the method's description here.
* creation date: (2001-2-4 20:34:37)
* @param urlenc java.lang.string
*/
private void parse(string urlenc,string encoding) throws java.io.ioexception
{
if (urlenc == null) return;
stringtokenizer tok = new stringtokenizer(urlenc,"&");
try{
while (tokelse{
name = apair;
value = "";
}
if(pairselse{
arraylist values = new arraylist();
values.add(value);
pairs.put(name,values);
}
}
}catch(exception e){
throw new java.io.ioexception(e.getmessage());
}
}
}
这个类的功能就是读取并保存form提交的信息,并实现常用的getparameter方法。
package com.hto.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* insert the type's description here.
* creation date: (2001-2-5 8:28:20)
* @author: 钱卫春
*/
public class utfbaseservlet extends httpservlet {
public static final string params_attr_name = "params_attr_name";
/**
* process incoming http get requests
*
* @param request object that encapsulates the request to the servlet
* @param response object that encapsulates the response from the servlet
*/
public void doget(httpservletrequest request, httpservletresponse response)
throws servletexception, ioexception {
performtask(request, response);
}
/**
* process incoming http post requests
*
* @param request object that encapsulates the request to the servlet
* @param response object that encapsulates the response from the servlet
*/
public void dopost(httpservletrequest request, httpservletresponse response)
throws servletexception, ioexception {
performtask(request, response);
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:52:43)
* @return int
* @param request javax.servlet.http.httpservletrequest
* @param name java.lang.string
* @param required boolean
* @param defvalue int
*/
public static java.sql.date getdateparameter(httpservletrequest request, str
ing name, boolean required, java.sql.date defvalue) throws servletexception{
string value = getparameter(request,name,required,string.valueof(defvalue));
return java.sql.date.valueof(value);
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:52:43)
* @return int
* @param request javax.servlet.http.httpservletrequest
* @param name java.lang.string
* @param required boolean
* @param defvalue int
*/
public static double getdoubleparameter(httpservletrequest request, string n
ame, boolean required, double defvalue) throws servletexception{
string value = getparameter(request,name,required,string.valueof(defvalue));
return double.parsedouble(value);
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:52:43)
* @return int
* @param request javax.servlet.http.httpservletrequest
* @param name java.lang.string
* @param required boolean
* @param defvalue int
*/
public static float getfloatparameter(httpservletrequest request, string nam
e, boolean required, float defvalue) throws servletexception{
string value = getparameter(request,name,required,string.valueof(defvalue));
return float.parsefloat(value);
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:52:43)
* @return int
* @param request javax.servlet.http.httpservletrequest
* @param name java.lang.string
* @param required boolean
* @param defvalue int
*/
public static int getintparameter(httpservletrequest request, string name, b
oolean required, int defvalue) throws servletexception{
string value = getparameter(request,name,required,string.valueof(defvalue));
return integer.parseint(value);
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:43:36)
* @return java.lang.string
* @param request javax.servlet.http.httpservletrequest
* @param name java.lang.string
* @param required boolean
* @param defvalue java.lang.string
*/
public static string getparameter(httpservletrequest request, string name, b
oolean required, string defvalue) throws servletexception{
if(request.getattribute(utfbaseservlet.params_attr_name) != null) {
utf8parameterreader params = (utf8parameterreader)request.getattribute(utfba
seservlet.params_attr_name);
if (params.getparameter(name) != null) return params.getparameter(name);
if (required) throw new servletexception("the parameter "+name+" required bu
t not provided!");
else return defvalue;
}else{
if (request.getparameter(name) != null) return request.getparameter(name);
if (required) throw new servletexception("the parameter "+name+" required bu
t not provided!");
else return defvalue;
}
}
/**
* returns the servlet info string.
*/
public string getservletinfo() {
return super.getservletinfo();
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:52:43)
* @return int
* @param request javax.servlet.http.httpservletrequest
* @param name java.lang.string
* @param required boolean
* @param defvalue int
*/
public static java.sql.timestamp gettimestampparameter(httpservletrequest re
quest, string name, boolean required, java.sql.timestamp defvalue) throws se
rvletexception{
string value = getparameter(request,name,required,string.valueof(defvalue));
return java.sql.timestamp.valueof(value);
}
/**
* initializes the servlet.
*/
public void init() {
// insert code to initialize the servlet here
}
/**
* process incoming requests for information
*
* @param request object that encapsulates the request to the servlet
* @param response object that encapsulates the response from the servlet
*/
public void performtask(httpservletrequest request, httpservletresponse resp
onse) {
try
{
// insert user code from here.
}
catch(throwable theexception)
{
// uncomment the following line when unexpected exceptions
// are occuring to aid in debugging the problem.
//theexception.printstacktrace();
}
}
/**
* insert the method's description here.
* creation date: (2001-2-5 8:31:54)
* @param request javax.servlet.servletrequest
* @param response javax.servlet.servletresponse
* @exception javax.servlet.servletexception the exception description.
* @exception java.io.ioexception the exception description.
*/
public void service(servletrequest request, servletresponse response) throws
javax.servlet.servletexception, java
}
这个就是servlet基类,它覆盖了父类的service方法,在调用父类service前,创建
了utf8parameterreader对象,其中保存了form中提交的信息。然后把这个对象作为一个
attribute保存到request对象中。然后照样调用父类的service方法。
对于继承这个类的servlet,要注意的是,"标准"getparameter在也不能读到post的
数据,因为在这之前这个类中已经从servletinputstream中读出了数据了。所以应该使
用该类中提供的getparameter方法。
剩下的就是输出问题了,我们要把输出的信息,转为utf8的二进制流输出。只要我
们设置content-type时指定charset为utf8,然后使用printwriter输出,那么这些转换
是自动进行的,servlet中这样设置:
response.setcontenttype("text/html;charset=utf8");
jsp中这样设置:
<%@ page contenttype="text/html;charset=utf8"%>
这样就可以保证输出是utf8流,客户端能否显示,就看客户端的了。
对于multipart/form-data的form提交的内容,我也提供一个类用来处理,在这个类
的构造子中可以指定页面使用的charset,默认还是utf-8,限于篇幅不贴出源码,如果
感兴趣可以mail to:vividq@china.com和我探讨。
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器