我們常見的亂碼大致有如下幾種情形:
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(嘉興)