我們常見(jiàn)的亂碼大致有如下幾種情形:
l 漢字變成了問(wèn)號(hào)“?”
l 有的漢字顯示正確,有的則顯示錯(cuò)誤
l 顯示亂碼(有些是漢字但并不是你預(yù)期的)
l 讀寫(xiě)數(shù)據(jù)庫(kù)出現(xiàn)亂碼
字符變問(wèn)號(hào)Java中byte與char相互轉(zhuǎn)換的方法在sun.io包中。其中,byte到char的常用轉(zhuǎn)換方法是:
public static ByteToCharConverter getConverter(String encoding);
為了便于大家理解,我們先來(lái)做一個(gè)小實(shí)驗(yàn):比如,漢字“你”的GBK編碼為0xc4e3,其Unicode編碼是"u4f60。我們的實(shí)驗(yàn)是這樣的,先有一個(gè)頁(yè)面比如名為a_gbk.jsp,在其中輸入漢字“你”,提交給頁(yè)面b_gbk.jsp。在b_gbk.jsp文件中以某種編碼方式得到“你”的字節(jié)數(shù)組,再將該數(shù)組以某種編碼方式轉(zhuǎn)換成char,如果得到的char值是0x4f60則轉(zhuǎn)換是正確的。
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%>
實(shí)驗(yàn)的步驟如下:
先在Tomcat服務(wù)器的子目錄webapps下建立一個(gè)名為charset目錄,當(dāng)然這個(gè)目錄可以任意命名,然后將a_gbk.jsp和b_gbk.jsp文件保存在該目錄下。啟動(dòng)Tomcat服務(wù)器,打開(kāi)IE瀏覽器,在地址欄中輸入http://127.0.0.1:8080/charset/a_gbk.jsp
此時(shí),將出現(xiàn)帶一個(gè)輸入文本和按鈕的頁(yè)面,在文本框中輸入“你”字(不包括雙引號(hào)),點(diǎn)擊OK按鈕,會(huì)得到如圖13.3所示的結(jié)果頁(yè)面。
現(xiàn)在對(duì)該結(jié)果做一些必要的解釋:
結(jié)合代碼清單13-2中可以看出:b_gbk.jsp文件,首先用一個(gè)名為reqValue的字符串變量取得從a_gbk.jsp的文本框的值。然后,用一個(gè)字節(jié)數(shù)組變量取得reqValue的字節(jié)值,并將它們打印在頁(yè)面上,這就是圖13.3中的第二、三行的c4和e3。接著在頁(yè)面上打印字節(jié)長(zhǎng)度,其結(jié)果是2,再又用ByteToCharConverter轉(zhuǎn)換器將字節(jié)轉(zhuǎn)換為字符數(shù)組并打印字符數(shù)組的長(zhǎng)度即字符數(shù)組中的字符個(gè)數(shù),其結(jié)果是1,表示是一個(gè)字符即‘你’。接下來(lái)打印字符的值是4f60,這正是我們前面提到的“你”字的Unicode編碼"u4f60。
這里要注意的是:byte b[]=reqValue.getBytes("ISO8859-1");中的編碼是ISO8859-1,這就是我們前面提到的有些web容器在您沒(méi)有指定request的字符集時(shí)它就采用缺省的ISO8859-1。
從圖中我們還看到表達(dá)式<%="reqValue是:"+reqValue%>中的reqValue并沒(méi)有正確地顯示“你”而是變成“??”這是什么原因呢?這里的reqValue是作為一個(gè)String被顯示的,我們來(lái)看看我們常用的String構(gòu)造函數(shù):
String(byte[] bytes,String encoding);
在國(guó)標(biāo)平臺(tái)上,該函數(shù)會(huì)認(rèn)為bytes是按GBK編碼的,如果后一個(gè)參數(shù)省略,它也會(huì)認(rèn)為是 encoding為GBK。
對(duì)前一個(gè)參數(shù)就相當(dāng)于將b_gbk.jsp文件的這句byte b[]=reqValue.getBytes("ISO8859-1");中的ISO8859-1改為GBK,這樣顯然在GBK字符集中找不到相應(yīng)的目的編碼,它給出的結(jié)果是0x3f、0x3f。因此,就會(huì)顯示為“??”,這也就是造成亂碼的第一種現(xiàn)象的原因。
顯然,造成這種亂碼是由于請(qǐng)求編碼采用默認(rèn)的ISO8859-1編碼所引起的,實(shí)驗(yàn)中的<%="reqValue1是:"+reqValue1%>語(yǔ)句中的reqValue1能正確顯示,給出了這個(gè)問(wèn)題的一種解決方法,即我們平常所說(shuō)的轉(zhuǎn)碼就是采用b_gbk.jsp中語(yǔ)句:
String reqValue1=new String(reqValue.getBytes("ISO8859-1"),"GBK");
這樣的做法,這也是早期中文jsp編程教材和編程實(shí)踐中曾被普遍采用的辦法。這種做法會(huì)使得應(yīng)用程序的代碼中充斥這種轉(zhuǎn)碼語(yǔ)句。其實(shí)存在著比這種做法更好一些的做法,就是在處理請(qǐng)求前,設(shè)置請(qǐng)求字符的編碼:
request.setCharacterEncoding("GBK");
我們繼續(xù)用實(shí)驗(yàn)來(lái)驗(yàn)證這種方法,將request.setCharacter- Encoding("GBK");這一句加到b_gbk.jsp文件中語(yǔ)句String reqValue=(String)request.getParameter("ClsID");的前一行,保存后再做前面的實(shí)驗(yàn),就會(huì)看到圖13.4所示的結(jié)果。
從圖13.4可以看出,原來(lái)不能正確顯示的reqValue可以正確顯示了,而原來(lái)所有正確的東西反而變得不正確了。造成這種結(jié)果的原因就是現(xiàn)在request的值的編碼不再是缺省的ISO8859-1,而是新設(shè)置的GBK。顯而易見(jiàn),要想使它們都正常,只要把b_gbk.jsp文件中的ISO8859-1都改為GBK就可以了。改動(dòng)后的文件如下:
代碼清單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%>
清單中的黑體部分是改動(dòng)的部分。
這種方法比起轉(zhuǎn)碼來(lái),前進(jìn)了一步,就是每個(gè)相關(guān)的.jsp文件在處理請(qǐng)求數(shù)據(jù)之前加上一句:request.setCharacterEncoding("GBK");就可以了。但是當(dāng)一個(gè)項(xiàng)目很大時(shí),要在所有相關(guān)的.jsp文件上加上這個(gè)代碼也還是比較麻煩的。更好的辦法是這個(gè)代碼設(shè)置在Servlet的過(guò)濾器(filter)中,語(yǔ)句如下:
ServletRequest.setCharacterEncoding("GBK");
后面會(huì)給出這個(gè)過(guò)濾器的完整示例代碼。
我們的例子是演示的從byte到char的轉(zhuǎn)換過(guò)程,相反的過(guò)程也會(huì)造成同樣的問(wèn)題,大家自己可以做類似的實(shí)驗(yàn)來(lái)驗(yàn)證。
部分漢字是亂碼
造成這個(gè)問(wèn)題的原因是,采用了GB2312的緣故,前面我們已經(jīng)講過(guò),GB2312只包含數(shù)千個(gè)字符。因此,有些不太常用的漢字如人名、地名中比較特別的漢字,在字符集中找不到對(duì)應(yīng)的編碼,故這部分漢字就無(wú)法正常顯示。可以用實(shí)驗(yàn)來(lái)驗(yàn)證這種情況:
將代碼清單13-3中的:
request.setCharacterEncoding("GBK");語(yǔ)句中的GBK改為GB2312再做實(shí)驗(yàn),將會(huì)發(fā)現(xiàn)在a_gbk.jsp中輸入“你”字可以正常顯示,但輸入朱镕基的“镕”字就不能正常顯示。因此,處理中文字符時(shí)不推薦使用GB2312,要么使用GBK,要么使用GB18030。
顯示亂碼(有些是漢字但并不是你預(yù)期的)
在上面,我們采用request.setCharacterEncoding("GBK");的方法已經(jīng)能比較成功地解決一些漢字亂碼問(wèn)題了。很顯然,用request.setCharacterEncoding("GBK");這種特定中文字符集的辦法只能解決中文相關(guān)問(wèn)題。而不能完整地解決I18N編程問(wèn)題。從前面介紹的字符集的基礎(chǔ)可以看出,統(tǒng)一使用UTF-8字符集不失為一種比較有效的方法。為了能比較直觀地說(shuō)明問(wèn)題,我們來(lái)接著做上面的實(shí)驗(yàn)。
將a_gbk.jsp中的GBK都替換為UTF-8,將action=b_gbk.jsp改為action=b.jsp。將文件另存為a.jsp,也放在與a_gbk.jsp相同的目錄下。因?yàn)槲募械腸ontentType="text/html; charset=UTF-8"字符集指定為UTF-8且我們又沒(méi)有指定pageEncoding的值,因此,存文件時(shí)編碼應(yīng)選擇為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相同的目錄下。與前面同樣的道理,保存時(shí)選擇編碼為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%>
在瀏覽器中訪問(wèn)a.jsp,在輸入框中輸入“你”,則結(jié)果頁(yè)面如圖13.5所示。
從圖13.5中可以看出,在UTF-8中一個(gè)漢字是用三個(gè)byte表示的,它們的值分別是0xe4、0xbd、0xa0,也就是說(shuō)用UTF-8來(lái)表示漢字,每個(gè)漢字要比GBK多占用一個(gè)byte,這也是使用UTF-8要多付出的一點(diǎn)代價(jià)吧。
有了上面這些鋪墊,現(xiàn)在有條件回答顯示亂碼(有些是漢字但并不是你預(yù)期的)的問(wèn)題了。
將代碼清單13-5中的語(yǔ)句:
String reqValue1=new String(reqValue.getBytes("UTF-8"),"UTF-8");
改為:
String reqValue1=new String(reqValue.getBytes("UTF-8"),"GBK");
保存后,再在a.jsp的輸入框中輸入“你”,您將看到圖13.6所示結(jié)果。
在圖13.6中,reqValue1的值變成了“浣?”,雖然其中包括漢字“浣”但這不是我們所期待的“你”字。您一定會(huì)問(wèn)為什么會(huì)出現(xiàn)“浣”字呢?
要回答這個(gè)問(wèn)題,您再瀏覽a_gbk.jsp文件,在輸入框中輸入“浣”字,您看到的結(jié)果將如圖13.7所示。
圖13.6 圖13.7
對(duì)比圖13.6與圖13.7可以清楚地看出,原來(lái),“你”的UTF-8的三個(gè)byte中的前兩個(gè)與“浣”的GBK編碼的兩個(gè)byte值相同。
String reqValue1=new String(reqValue.getBytes("UTF-8"),"GBK");在構(gòu)成字符串時(shí),來(lái)了一個(gè)張冠李戴,將“你”的UTF-8的三個(gè)byte中的前兩個(gè)byte構(gòu)造成“浣”,后面的一個(gè)byte不能對(duì)應(yīng)其他漢字就變成了“?”,因此,“你”就陰錯(cuò)陽(yáng)差地變成了“浣?”。這就是顯示亂碼的原因。當(dāng)然這種現(xiàn)象并不僅僅出現(xiàn)在UTF-8與GBK之間,但隱藏在這些現(xiàn)象背后的原理都是相同的。
讀寫(xiě)數(shù)據(jù)庫(kù)時(shí)出現(xiàn)亂碼
現(xiàn)在一些常用的數(shù)據(jù)庫(kù)都支持?jǐn)?shù)據(jù)庫(kù)encoding,也就是說(shuō)在創(chuàng)建數(shù)據(jù)庫(kù)時(shí)可以指定它自己的字符集設(shè)置,數(shù)據(jù)庫(kù)數(shù)據(jù)以指定的編碼形式存儲(chǔ)。當(dāng)應(yīng)用程序訪問(wèn)數(shù)據(jù)庫(kù)時(shí),在入口和出口處都會(huì)有encoding轉(zhuǎn)換。如果在應(yīng)用程序中字符本來(lái)已變成了亂碼,當(dāng)然也就無(wú)法正確地轉(zhuǎn)換為數(shù)據(jù)庫(kù)的字符集了。數(shù)據(jù)庫(kù)的encoding可根據(jù)需要來(lái)設(shè)置,比如要支持簡(jiǎn)、繁體中文、日、韓、英語(yǔ)選GBK,如果還要支持其他語(yǔ)言最好選UTF-8。
柴油發(fā)電機(jī)
發(fā)電機(jī)
柴油機(jī)
柴油發(fā)電機(jī)
13636374743(上海)
13291526067(嘉興)