1. 程式人生 > >JAVA中文字元編碼問題詳解

JAVA中文字元編碼問題詳解

new InputStreamReader(newFileInputStream("D://test.txt"),"utf-8"); 
BufferedReader reader = new BufferedReader(sr);
這樣,JAVA就會用utf-8的方式來從檔案中讀取字元資料。

另外,我們可以通過在java命令後帶上Dfile.encoding引數來指定虛擬機器讀取檔案使用的預設字元編碼,例如java -Dfile.encoding=utf-8 Test,這樣,我們在JAVA程式碼裡用System.getProperty("file.encoding")取到的值為utf-8。

四、JSP讀取request.getParameter裡的中文引數後,在頁面顯示為亂碼。
 
首先我們來分析用GET方式上傳引數的亂碼情況。
例如我們在瀏覽器位址列輸入以下URL:http://localhost:8080/test/test.jsp?param=大家好
我們的JSP程式碼如此處理param這個引數:
<% String text = request.getParameter("param"); %>
<%=text%>
而就這麼簡單的兩句程式碼,我們很有可能在頁面上看到這樣的亂碼:??????
這裡給大家分析一下到底是怎麼一回事。
首先,我們來看看與request物件有哪些相關的編碼設定:
1. JSP檔案的字元編碼
2. 請求這個帶引數URL的源頁面的字元編碼
3. IE的高階設定中的選項"總以utf-8方式傳送URL地址"
4. TOMCAT的server.xml中配置URIEncoding
5. 函式request.setCharacterEncoding()
6. JS的encodeURIComponent函式與JAVA的URLDecoder類
IE的"總以utf-8方式傳送URL地址"設定並不影響對parameter的解析,而從頁面請求URL和從位址列輸入URL居然也有不同的表現。

結論:
1. IE設定中的"總以utf-8方式傳送URL地址"只對URL的PATH部分起作用
 ,對查詢字串是不起作用的 。也就是說,如果勾選了這個選項,那麼類似http://localhost:8080/test/大家好.jsp?param=大家好這種URL,前一個"大家好"將被轉化成utf-8形式,而後一個並沒有變化。這裡所說的utf-8形式,其實應該叫utf-8+escape形式,即%B4%F3%BC%D2%BA%C3這種形式。
那麼,查詢字串中的中文字元 ,到底是 什麼編碼傳送到伺服器的呢?答案是系統預設編碼 ,即GBK。也就是說,在我們中文作業系統上,傳送給WEB伺服器的查詢字串,總是以GBK來編碼的。

2. 在頁面中通過連結或location重定向 或open新視窗的方式來請求一個URL,這個URL裡面的中文字元
 是用什麼編碼的?答:是用該頁面的編碼型別。 也就是說,如果我們從某個源JSP頁面上的連結來訪問http://localhost:8080/test/test.jsp?param=大家好這個URL,如果源JSP頁面的編碼是UTF-8,則大家好這幾個字的編碼就是UTF-8。
而在位址列上直接輸入URL地址,或者從系統剪貼簿貼上到位址列上 ,這個輸入並非從頁面中發起的,而是由作業系統發起的,所以這個編碼只可能是系統的預設編碼 ,與任何頁面無關。我們還發現,在不同的瀏覽器上,用連結方式開啟的頁面,如果在位址列上再敲個回車,顯示的結果也會不同。IE上敲回車後顯示不變化,而傲游上可能就會有亂碼或亂碼消失的變化。說明IE上敲回車,實際傳送的是之前記憶下來的記憶體中的URL,而傲游上傳送的從當前位址列重新獲取的 URL。

3. TOMCAT的URIEncoding如果不加以設定,則預設使用ISO-8859-1來解碼URL ,設定後便用設定了的編碼方式來解碼。這個解碼同時包括PATH部分和查詢字串部分。可見,這個引數是對用GET方式傳 遞的中文引數最關鍵的設定。不過,這個引數只對GET方式傳遞的引數有效 ,對POST 的無效 。分析TOMCAT的原始碼我們可以看到,在請求一個頁面時,TOMCAT會嘗試構造一個Request物件,在這個物件裡,會從 Server.xml裡讀取URIEncoding的值,並賦值給Parameters類的queryStringEncoding變數,而這個變數將在 解析request.getParameter中的GET引數時用來指導字元解碼。

4. request.setCharacterEncoding函式只對POST的引數有效,對GET的引數無效 。且這個函式必須是在第一次呼叫 request.getParameter之前 使用。這是因為Parameters類有兩個字元編碼引數,一個是encoding,另一個是 queryStringEncoding,而setCharacterEncoding設定的是encoding,這個是在解析POST的引數是才用到 的。
所以,這就導致了我們通常都要分開處理POST和GET的字元編碼 ,用TOMCAT自帶的filter只能處理POST的,另外要設定URIEncoding來設定GET的。這樣很麻煩而且URIEncoding無法根據內容來動態區分編碼,總還是一個問題。
在調查TOMCAT的程式碼時發現了另一個在server.xml裡的引數useBodyEncodingForURI ,可以解決這個問題。這個引數設成 true後,TOMCAT就會用request.setCharacterEncoding所設定的字元編碼來同樣解析GET引數了。這樣,那個 SetCharacterEncodingFilter就可以同時處理GET和POST引數了

知道了以上知識後,我們再來分析一下前面表格中列出的幾個典型現象。
第一條,請求源頁面的編碼為UTF-8,而TOMCAT的URIEncoding未指定,則TOMCAT用ISO8859-1方式來解碼引數,所以從request中讀出來後,記憶體中儲存的為錯誤的UNICODE資料,導致之後到螢幕顯示的所有轉換全部出錯。
第九條,請求源頁面編碼為GBK,而TOMCAT的URIEncoding也為GBK,TOMCAT用GBK方式去解碼原本用GBK編碼的字元,解碼正確,記憶體中的UNICODE值正確,最終顯示正確的中文。
第十三條,請求源頁面編碼為UTF-8,TOMCAT的URIEncoding也為UTF-8,而在IE6中最終顯示的中文字元,如果是奇數個數,則最後一個會顯示為亂碼。這是為什麼呢?
我的猜測是,這是因為IE6將URL地址傳送時,對查詢字串是直接對UTF-8格式的字元使用GBK來編碼,而不是對UNICODE的字元來用GBK編 碼,所以UTF-8的資料沒有經過UNICODE而直接編碼成了GBK。而到了TOMCAT這邊,GBK的編碼又被當成UTF-8做了解碼。所以這個過程 中經過了UTF-8轉換成GBK,然後又從GBK轉換成UTF-8的過程,而這種轉換,恰好就會出現奇數箇中文字串的最後一位為亂碼的現象。而在IE7 中,估計把這種現象當做BUG已經被解決了,即在傳送地址時會先轉成UNICODE再編碼成GBK。那麼估計在IE7的瀏覽器+中文作業系統環境下,如果 我們把TOMCAT的URIEncoding設定成GBK,無論JSP編碼成什麼格式,都不會出現亂碼。這個沒測試,請大家自己驗證。

五、對URL做Encode和Decode 
對於request引數的中文亂碼問題,個人覺得最好的還是用URLEncode/URLDecode ,因為如果你的WEB站點要支援國際化,最好就是保證從IE遞送過來的引數永遠是正確的UTF-8編碼。
在IE端,我們可以用JS 指令碼來對引數編碼:encodeURIComponent() ,編碼後中文字元便變成了%B4%F3%BC%D2%BA%C3這種形式。在JAVA端 ,可以用java.net.URLDecoder.decode 來解碼。不過這裡要注意一個問題,就是TOMCAT會自動先對URL 做一次decode ,我們可以在TOMCAT的UDecoder類中看到這一點。不過TOMCAT並非使用了URLDecoder.decode,而是自 己編寫了一個decode函式。網上有些文章上介紹過一種處理亂碼的方法便是在JS中對引數做兩次encodeURIComponent ,在JAVA中做 一次decode ,可以解決一些沒有設定URIEncoding時發生的亂碼問題。不過個人覺得如果弄懂了整個字元編碼轉換的過程,基本上是用不到這種方法的。

六、從資料庫中讀取中文字元資料,在頁面上顯示為亂碼。
對於資料庫中讀取中文字元出現亂碼的問題遇到的還比較少
名稱 特點 產生原因
古文碼 大都為不認識的古文,並加雜日韓文 以GBK方式讀取UTF-8編碼的中文
口字碼 大部分字元為小方塊 以UTF-8的方式讀取GBK編碼的中文 
符號碼 大部分字元為各種符號 以ISO8859-1方式讀取UTF-8編碼的中文
拼音碼 大部分字元為頭頂帶有各種類似聲調符號的字母 以ISO8859-1方式讀取GBK編碼的中文
問句碼 長度為偶數時正確,為奇數時最後的字元變為問號 以GBK方式讀取UTF-8編碼的中文,然後又用UTF-8的格式再次讀取 
錕拷碼 全中文字元,且大部分字元為“錕斤拷”這幾個字元 以UTF-8方式讀取GBK編碼的中文,然後又用GBK的格式再次讀取