我们常见的乱码大致有如下几种情形:
l 汉字变成了问号“?”
l 有的汉字显示正确,有的则显示错误
l 显示乱码(有些是汉字但并不是你预期的)
l 读写数据库出现乱码
字符变问号
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的代码如下:
代码清单13-1
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td> </td>
<td > </td>
<td> </td>
</tr>
<tr>
<td width="100"> </td>
<td >Input</td>
<td width="100"> </td>
</tr>
<tr>
<td> </td>
<td > </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" 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的代码如下:
代码清单13-2
<%@ page contentType="text/html; charset=GBK" import="sun.io.*" %>
<%
String reqValue=(String)request.getParameter("ClsID");
byte b[]=reqValue.getBytes("ISO8859-1");
out.println("print byte value<br>");
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("byte length:"+b.length+"<br>");
out.println("char length:"+c.length+"<br>");
out.println("print char value<br>");
for(int i=0;i<c.length;i++){
out.println(Integer.toHexString(c[i])+"<br>");
}
String reqValue1=new String(reqValue.getBytes("ISO8859-1"),"GBK");
%>
<%="reqValue是:"+reqValue%><br>
<%="reqValue1是:"+reqValue1%>
实验的步骤如下:
先在Tomcat服务器的子目录webapps下建立一个名为charset目录,当然这个目录可以任意命名,然后将a_gbk.jsp和b_gbk.jsp文件保存在该目录下。启动Tomcat服务器,打开IE浏览器,在地址栏中输入http://127.0.0.1:8080/charset/a_gbk.jsp
此时,将出现带一个输入文本和按钮的页面,在文本框中输入“你”字(不包括双引号),点击OK按钮,会得到如图13.3所示的结果页面。
现在对该结果做一些必要的解释:
结合代码清单13-2中可以看出:b_gbk.jsp文件,首先用一个名为reqValue的字符串变量取得从a_gbk.jsp的文本框的值。然后,用一个字节数组变量取得reqValue的字节值,并将它们打印在页面上,这就是图13.3中的第二、三行的c4和e3。接着在页面上打印字节长度,其结果是2,再又用ByteToCharConverter转换器将字节转换为字符数组并打印字符数组的长度即字符数组中的字符个数,其结果是1,表示是一个字符即‘你’。接下来打印字符的值是4f60,这正是我们前面提到的“你”字的Unicode编码"u4f60。
这里要注意的是:byte b[]=reqValue.getBytes("ISO8859-1");中的编码是ISO8859-1,这就是我们前面提到的有些web容器在您没有指定request的字符集时它就采用缺省的ISO8859-1。
从图中我们还看到表达式<%="reqValue是:"+reqValue%>中的reqValue并没有正确地显示“你”而是变成“??”这是什么原因呢?这里的reqValue是作为一个String被显示的,我们来看看我们常用的String构造函数:
String(byte[] bytes,String encoding);
在国标平台上,该函数会认为bytes是按GBK编码的,如果后一个参数省略,它也会认为是 encoding为GBK。
对前一个参数就相当于将b_gbk.jsp文件的这句byte b[]=reqValue.getBytes("ISO8859-1");中的ISO8859-1改为GBK,这样显然在GBK字符集中找不到相应的目的编码,它给出的结果是0x3f、0x3f。因此,就会显示为“??”,这也就是造成乱码的第一种现象的原因。
显然,造成这种乱码是由于请求编码采用默认的ISO8859-1编码所引起的,实验中的<%="reqValue1是:"+reqValue1%>语句中的reqValue1能正确显示,给出了这个问题的一种解决方法,即我们平常所说的转码就是采用b_gbk.jsp中语句:
String reqValue1=new String(reqValue.getBytes("ISO8859-1"),"GBK");
这样的做法,这也是早期中文jsp编程教材和编程实践中曾被普遍采用的办法。这种做法会使得应用程序的代码中充斥这种转码语句。其实存在着比这种做法更好一些的做法,就是在处理请求前,设置请求字符的编码:
request.setCharacterEncoding("GBK");
我们继续用实验来验证这种方法,将request.setCharacter- Encoding("GBK");这一句加到b_gbk.jsp文件中语句String reqValue=(String)request.getParameter("ClsID");的前一行,保存后再做前面的实验,就会看到图13.4所示的结果。
从图13.4可以看出,原来不能正确显示的reqValue可以正确显示了,而原来所有正确的东西反而变得不正确了。造成这种结果的原因就是现在request的值的编码不再是缺省的ISO8859-1,而是新设置的GBK。显而易见,要想使它们都正常,只要把b_gbk.jsp文件中的ISO8859-1都改为GBK就可以了。改动后的文件如下:
代码清单13-3
<%@ page contentType="text/html; charset=GBK" import="sun.io.*" %>
<%
request.setCharacterEncoding("GBK");
String reqValue=(String)request.getParameter("ClsID");
byte b[]=reqValue.getBytes("GBK");
out.println("print byte value<br>");
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("byte length:"+b.length+"<br>");
out.println("char length:"+c.length+"<br>");
out.println("print char value<br>");
for(int i=0;i<c.length;i++){
out.println(Integer.toHexString(c[i])+"<br>");
}
String reqValue1=new String(reqValue.getBytes("GBK"),"GBK");
%>
<%="reqValue是:"+reqValue%><br>
<%="reqValue1是:"+reqValue1%>
清单中的黑体部分是改动的部分。
这种方法比起转码来,前进了一步,就是每个相关的.jsp文件在处理请求数据之前加上一句:request.setCharacterEncoding("GBK");就可以了。但是当一个项目很大时,要在所有相关的.jsp文件上加上这个代码也还是比较麻烦的。更好的办法是这个代码设置在Servlet的过滤器(filter)中,语句如下:
ServletRequest.setCharacterEncoding("GBK");
后面会给出这个过滤器的完整示例代码。
我们的例子是演示的从byte到char的转换过程,相反的过程也会造成同样的问题,大家自己可以做类似的实验来验证。
部分汉字是乱码
造成这个问题的原因是,采用了GB2312的缘故,前面我们已经讲过,GB2312只包含数千个字符。因此,有些不太常用的汉字如人名、地名中比较特别的汉字,在字符集中找不到对应的编码,故这部分汉字就无法正常显示。可以用实验来验证这种情况:
将代码清单13-3中的:
request.setCharacterEncoding("GBK");语句中的GBK改为GB2312再做实验,将会发现在a_gbk.jsp中输入“你”字可以正常显示,但输入朱镕基的“镕”字就不能正常显示。因此,处理中文字符时不推荐使用GB2312,要么使用GBK,要么使用GB18030。
显示乱码(有些是汉字但并不是你预期的)
在上面,我们采用request.setCharacterEncoding("GBK");的方法已经能比较成功地解决一些汉字乱码问题了。很显然,用request.setCharacterEncoding("GBK");这种特定中文字符集的办法只能解决中文相关问题。而不能完整地解决I18N编程问题。从前面介绍的字符集的基础可以看出,统一使用UTF-8字符集不失为一种比较有效的方法。为了能比较直观地说明问题,我们来接着做上面的实验。
将a_gbk.jsp中的GBK都替换为UTF-8,将action=b_gbk.jsp改为action=b.jsp。将文件另存为a.jsp,也放在与a_gbk.jsp相同的目录下。因为文件中的contentType="text/html; charset=UTF-8"字符集指定为UTF-8且我们又没有指定pageEncoding的值,因此,存文件时编码应选择为UTF-8。更改后的文件代码如下:
代码清单13-4
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td> </td>
<td > </td>
<td> </td>
</tr>
<tr>
<td width="100"> </td>
<td >Input</td>
<td width="100"> </td>
</tr>
<tr>
<td> </td>
<td > </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" 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中的GBK全部替换为UTF-8,另存为b.jsp,放在与b_gbk.jsp相同的目录下。与前面同样的道理,保存时选择编码为UTF-8。更改后的代码如下:
代码清单13-5
<%@ page contentType="text/html; charset=UTF-8" import="sun.io.*" %>
<%
request.setCharacterEncoding("UTF-8");
String reqValue=(String)request.getParameter("ClsID");
byte b[]=reqValue.getBytes("UTF-8");
out.println("print byte value<br>");
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("byte length:"+b.length+"<br>");
out.println("char length:"+c.length+"<br>");
out.println("print char value<br>");
for(int i=0;i<c.length;i++){
out.println(Integer.toHexString(c[i])+"<br>");
}
String reqValue1=new String(reqValue.getBytes("UTF-8"),"UTF-8");
%>
<%="reqValue是:"+reqValue%><br>
<%="reqValue1是:"+reqValue1%>
在浏览器中访问a.jsp,在输入框中输入“你”,则结果页面如图13.5所示。
从图13.5中可以看出,在UTF-8中一个汉字是用三个byte表示的,它们的值分别是0xe4、0xbd、0xa0,也就是说用UTF-8来表示汉字,每个汉字要比GBK多占用一个byte,这也是使用UTF-8要多付出的一点代价吧。
有了上面这些铺垫,现在有条件回答显示乱码(有些是汉字但并不是你预期的)的问题了。
将代码清单13-5中的语句:
String reqValue1=new String(reqValue.getBytes("UTF-8"),"UTF-8");
改为:
String reqValue1=new String(reqValue.getBytes("UTF-8"),"GBK");
保存后,再在a.jsp的输入框中输入“你”,您将看到图13.6所示结果。
在图13.6中,reqValue1的值变成了“浣�”,虽然其中包括汉字“浣”但这不是我们所期待的“你”字。您一定会问为什么会出现“浣”字呢?
要回答这个问题,您再浏览a_gbk.jsp文件,在输入框中输入“浣”字,您看到的结果将如图13.7所示。
图13.6 图13.7
对比图13.6与图13.7可以清楚地看出,原来,“你”的UTF-8的三个byte中的前两个与“浣”的GBK编码的两个byte值相同。
String reqValue1=new String(reqValue.getBytes("UTF-8"),"GBK");在构成字符串时,来了一个张冠李戴,将“你”的UTF-8的三个byte中的前两个byte构造成“浣”,后面的一个byte不能对应其他汉字就变成了“�”,因此,“你”就阴错阳差地变成了“浣�”。这就是显示乱码的原因。当然这种现象并不仅仅出现在UTF-8与GBK之间,但隐藏在这些现象背后的原理都是相同的。
读写数据库时出现乱码
现在一些常用的数据库都支持数据库encoding,也就是说在创建数据库时可以指定它自己的字符集设置,数据库数据以指定的编码形式存储。当应用程序访问数据库时,在入口和出口处都会有encoding转换。如果在应用程序中字符本来已变成了乱码,当然也就无法正确地转换为数据库的字符集了。数据库的encoding可根据需要来设置,比如要支持简、繁体中文、日、韩、英语选GBK,如果还要支持其他语言最好选UTF-8。
柴油发电机
发电机
柴油机
柴油发电机
13636374743(上海)
13291526067(嘉兴)