二、中文字符乱码的原因及解决办法
java的内核是Unicode的,也就是说,在程序处理字符时是用Unicode来表示字符的,但是文件和流的保存方式是使用字节流的。在java的基本数据类型中,char是Unicode的,而byte是字节,因此,在不同的环节java要对字节流和char进行转换。这种转换发生时如果字符集的编码选择不当,就会出现乱码问题。
我们常见的乱码大致有如下几种情形:
1、汉字变成了问号"?"
2、有的汉字显示正确,有的则显示错误
3、显示乱码(有些是汉字但并不是你预期的)
4、读写数据库出现乱码
下面我们逐一对它们出现的原因做一些解释:
首先,我们讨论汉字变成问号的问题。
Java中byte与char相互转换的方法在sun.io包中。其中,byte到char的常用转换方法是:
public static ByteToCharConverter getConverter(String encoding);
为了便于大家理解,我们先来做一个小实验:比如,汉字"你"的GBK编码为0xc4e3,其Unicode编码是\u4f60。我们的实验是这样的,先有一个页面比如名为a_gbk.jsp输入汉字"你",提交给页面b_gbk.jsp。在b_gbk.jsp文件中以某种编码方式得到"你"的字节数组,再将该数组以某种编码方式转换成char,如果得到的char值是0x4f60则转换是正确的。
a_gbk.jsp的代码如下:
<%@ page contentType="text/html; charset=GBK" language="java" import="java.sql.*" errorPage="" %>
<table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td> </td>
<td class="bigword"> </td>
<td> </td>
</tr>
<tr>
<td width="100"> </td>
<td class="bigword">Input</td>
<td width="100"> </td>
</tr>
<tr>
<td> </td>
<td class="bigword"> </td>
<td> </td>
</tr>
</table>
<table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td><form method="post" action="b_gbk.jsp">
<table width="611" border="0" cellpadding="0" cellspacing="0">
<tr>
<td width="100" align="right"></td>
<td><input name="ClsID" type="text" class="word" id="ClsID" maxlength="2" >
*</td>
</tr>
<tr>
<td width="100" align="right"> </td>
<td><input name="btn" type="submit" value="OK">
</td>
</tr>
</table>
</form></td>
</tr>
</table> |
b_gbk.jsp的代码如下:
<%@ page contentType="text/html; charset=GBK" import="sun.io.*,java.util.*" %>
<%
String a=(String)request.getParameter("ClsID");
byte b[]=a.getBytes("ISO8859-1");
for(int j=0;j<b.length;j++){
out.println(Integer.toHexString(b[j])+"<br>");
}
ByteToCharConverter convertor=ByteToCharConverter.getConverter("GBK");
char[] c=convertor.convertAll(b);
out.println("b length:"+b.length+"<br>");
out.println("c length:"+c.length+"<br>");
for(int i=0;i<c.length;i++){
out.println(Integer.toHexString(c[i])+"<br>");
}
String a1=new String(a.getBytes("ISO8859-1"),"GBK");
%>
<%="a是:"+a%><br>
<%="a1是:"+a1%> |
在浏览器中打开a_gbk.jsp并输入一个"你"字,点击OK按钮提交表单,则会出现如图1所示的结果:
图1
从图1可以看出,在b_gbk.jsp中这样将byte转换为char是正确的,即得到的char是\u4f60。这里要注意的是:byte b[]=a.getBytes("ISO8859-1");中的编码是ISO8859-1,这就是我们前面提到的有些web容器在您没有指定request的字符集时它就采用缺省的ISO8859-1。
从图1中我们还看到表达式中的a并没有正确地显示"你"而是变成"??"这是什么原因呢?这里的a是作为一个String被显示的,我们来看看我们常用的String构造函数:
String(byte[] bytes,String encoding);
在国标平台上,该函数会认为bytes是按GBK编码的,如果后一个参数省略,它也会认为是encoding是GBK。
对前一个参数就相当于将b_gbk.jsp文件的这句byte b[]=a.getBytes("ISO8859-1");中的ISO8859-1改为GBK,这样显然在GBK字符集中找不到相应的目的编码,它给出的结果是0x3f、0x3f。因此,就会显示为"??",这也就是造成乱码的第一种现象的原因。我们的例子是演示的从byte到char的转换过程,相反的过程也会造成同样的问题,限于篇幅,就不在此讨论了,大家自己可以做类似的实验来验证。
解决该问题的方法就是象例子中a1那样,在获取byte数组时,指定编码为ISO8859-1。
接下来,我们讨论有些汉字能正常显示,有些不能正常显示的问题。
如果我们将String a1=new String(a.getBytes("ISO8859-1"),"GBK");中的GBK改为GB2312则象朱镕基的"镕"字就不能正常显示,这是因为该字是GBK中的字符而在GB2312中不存在。
解决上述两种问题的方法就是象a1那样构造String,也就是人们常说的同时也是常用的转码的方法。采用这种方法会在程序中到处出现这种语句,特别是在Struts中,Struts有一个回写表单的功能,在回写时也要做这种转换,这样的语句差不多要多一倍。因此,这是个比较笨拙的方法,有没有简捷一些的方法呢?
其实是有的,只要在取得request的字符串前加上request.setCharacterEncoding("GBK");这句,指定request的字符集。则中的a就能正常显示,a1反而不能正常显示。此时要将byte b[]=a.getBytes("ISO8859-1");中的ISO8859-1变成GBK,从byte到char的转换才是正确的,这就是此时a能正常显示而a1反而不能正常显示的原因。如果此时要a1正常显示则必须将String a1=new String(a.getBytes("ISO8859-1"),"GBK");中的ISO8859-1改为GBK。
很显然,使用request.setCharacterEncoding("GBK");只能解决GBK字符问题,要解决i18n问题则要使用UTF-8来取代GBK。我们接着做上述实验,将a_gbk.jsp和b_gbk.jsp分别另存为a.jsp和b.jsp将文件中的GBK改为UTF-8,更改后的代码分别如下:
a.jsp代码:
<%@ page contentType="text/html; charset=UTF-8" language="java" import="java.sql.*" errorPage="" %>
<table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td> </td>
<td class="bigword"> </td>
<td> </td>
</tr>
<tr>
<td width="100"> </td>
<td class="bigword">Input</td>
<td width="100"> </td>
</tr>
<tr>
<td> </td>
<td class="bigword"> </td>
<td> </td>
</tr>
</table>
<table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td><form method="post" action="b.jsp">
<table width="611" border="0" cellpadding="0" cellspacing="0">
<tr>
<td width="100" align="right"></td>
<td><input name="ClsID" type="text" class="word" id="ClsID" maxlength="2" >
*</td>
</tr>
<tr>
<td width="100" align="right"> </td>
<td><input name="btn" type="submit" value="OK">
</td>
</tr>
</table>
</form></td>
</tr>
</table>
b.jsp代码:
<ccid_nobr>
<table width="400" border="1" cellspacing="0" cellpadding="2"
bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center">
<tr>
<td bgcolor="e6e6e6" class="code" style="font-size:9pt">
<pre><ccid_code> <%@ page contentType="text/html; charset=UTF-8" import="sun.io.*,java.util.*" %>
<%
request.setCharacterEncoding("UTF-8");
String a=(String)request.getParameter("ClsID");
byte b[]=a.getBytes("UTF-8");
for(int j=0;j<b.length;j++){
out.println(Integer.toHexString(b[j])+"<br>");
}
ByteToCharConverter convertor=ByteToCharConverter.getConverter("UTF-8");
char[] c=convertor.convertAll(b);
out.println("b length:"+b.length+"<br>");
out.println("c length:"+c.length+"<br>");
for(int i=0;i<c.length;i++){
out.println(Integer.toHexString(c[i])+"<br>");
}
String a1=new String(a.getBytes("UTF-8"),"UTF-8");
%>
<%="a是:"+a%><br>
<%="a1是:"+a1%> |
再在a.jsp中输入"你"字,你会发现显示结果中,一个汉字是用三个byte表示的,它们的值分别是0xe4、0xbd、0xa0,
也就是说用UTF-8来表示汉字,每个汉字要比GBK多占用一个byte,这也是使用UTF-8要多付出的一点代价吧。
现在,我们讨论一下第三个问题,即显示乱码,有些莫名其妙的汉字并不是你预期的结果。
在上例中将String a1=new String(a.getBytes("UTF-8"),"UTF-8");改为String a1=new String(a.getBytes("UTF-8"),"GBK");再输入"你"字,则a1会显示成"浣?",您只要看一看"浣"的UTF-8码和GBK码就会知道其中的奥秘了。
下面,我们讨论一下最后一个问题,就是读写数据库时出现乱码。
现在一些常用的数据库都支持数据库encoding,也就是说在创建数据库时可以指定它自己的字符集设置,数据库数据以指定的编码形式存储。当应用程序访问数据库时,在入口和出口处都会有encoding转换。如果,在应用程序中字符本来已变成了乱码,当然也就无法正确地转换为数据库的字符集了。数据库的encoding可根据需要来设置,比如要支持简、繁体中文、日、韩、英语选GBK,如果还要支持其他语言最好选UTF-8。