Java中文字編碼問題詳解

地址:http://blog.csdn.net/jlhnxly/article/details/6323654

JAVA的中文字符亂碼問題一直很讓人頭疼。特別是在WEB應用中。網上的分析文章和解決方案都很多,但總是針對某些特定情況的。很多次遇到亂碼問題後,經過極爲辛苦的調試和搜索資料後終於解決,滿以爲自己已經掌握了對付這些字符亂碼怪獸的訣竅。可當過段時間,換了個應用或換了個環境,又會碰到那討厭的火星文,並再次無所適從。於是下決心好好整理一下中文字符編碼問題,以方便自己記憶,也爲其他程序員兄弟們提供一份參考。
   
    首先要了解JAVA處理字符的原理。JAVA使用UNICODE來存儲字符數據,處理字符時通常有三個步驟:
    - 按指定的字符編碼形式,從源輸入流中讀取字符數據
    - 以UNICODE編碼形式將字符數據存儲在內存中
    - 按指定的字符編碼形式,將字符數據編碼並寫入目的輸出流中。
    所以JAVA處理字符時總是經過了兩次編碼轉換,一次是從指定編碼轉換爲UNICODE編碼,一次是從UNICODE編碼轉換爲指定編碼。如果在讀入時用錯誤的形式解碼字符,則內存存儲的是錯誤的UNICODE字符。而從最初文件中讀出的字符數據,到最終在屏幕終端顯示這些字符,期間經過了應用程序的多次轉換。如果中間某次字符處理,用錯誤的編碼方式解碼了從輸入流讀取的字符數據,或用錯誤的編碼方式將字符寫入輸出流,則下一個字符數據的接收者就會編解碼出錯,從而導致最終顯示亂碼。
    這一點,是我們分析字符編碼問題以及解決問題的指導思想。
   
    好,現在我們開始一隻只的解決這些亂碼怪獸。
   
    一、在JAVA文件中硬編碼中文字符,在eclipse中運行,控制檯輸出了亂碼。
    例如,我們在JAVA文件中寫入以下代碼:
    String text = "大家好";
  System.out.println(text);
  如果我們是在eclipse裏編譯運行,可能看到的結果是類似這樣的亂碼:��Һ�。那麼,這是爲什麼呢?
 
  我們先來看看整個字符的轉換過程。
  1. 在eclipse窗口中輸入中文字符,並保存成UTF-8的JAVA文件。這裏發生了多次字符編碼轉換。不過因爲我們相信eclipse的正確性,所以我們不用分析其中的過程,只需要相信保存下的JAVA文件確實是UTF-8格式。
  2. 在eclipse中編譯運行此JAVA文件。這裏有必要詳細分析一下編譯和運行時的字符編碼轉換。
   - 編譯:我們用javac編譯JAVA文件時,javac不會智能到猜出你所要編譯的文件是什麼編碼類型的,所以它需要指定讀取文件所用的編碼類型。默認javac使用平臺缺省的字符編碼類型來解析JAVA文件。平臺缺省編碼是操作系統決定的,我們使用的是中文操作系統,語言區域設置通常都是中國大陸,所以平臺缺省編碼類型通常是GBK。這個編碼類型我們可以在JAVA中使用System.getProperty("file.encoding")來查看。所以javac會默認使用GBK來解析JAVA文件。如果我們要改變javac所用的編碼類型,就要加上-encoding參數,如javac -encoding utf-8 Test.java。
     這裏要另外提一下的是eclipse使用的是內置的編譯器,並不能添加參數,如果要爲javac添加參數則建議使用ANT來編譯。不過這並非出現亂碼的原因,因爲eclipse可以爲每個JAVA文件設置字符編碼類型,而內置編譯器會根據此設置來編譯JAVA文件。
   - 運行:編譯後字符數據會以UNICODE格式存入字節碼文件中。然後eclipse會調用java命令來運行此字節碼文件。因爲字節碼中的字符總是UNICODE格式,所以java讀取字節碼文件並沒有編碼轉換過程。虛擬機讀取文件後,字符數據便以UNICODE格式存儲在內存中了。
  3. 調用System.out.println來輸出字符。這裏又發生了字符編碼轉換。
  System.out.println使用了PrintStream類來輸出字符數據至控制檯。PrintStream會使用平臺缺省的編碼方式來輸出字符。我們的中文系統上缺省方式爲GBK,所以內存中的UNICODE字符被轉碼成了GBK格式,並送到了操作系統的輸出服務中。因爲我們操作系統是中文系統,所以往終端顯示設備上打印字符時使用的也是GBK編碼。如果到這一步,我們的字符其實不再是GBK編碼的話,終端就會顯示出亂碼。
 
  那麼,在eclipse運行帶中文字符的JAVA文件,控制檯顯示了亂碼,是在哪一步轉換錯誤呢?我們一步步來分析。
  - 保存JAVA文件成UTF-8後,如果再次打開你沒有看到亂碼,說明這步是正確的。
  - 用eclipse本身來編譯運行JAVA文件,應該沒有問題。
  - System.out.println會把內存中正確的UNICODE字符編碼成GBK,然後發到eclipse的控制檯去。等等,我們看到在Run Configuration對話框的Common標籤裏,控制檯的字符編碼被設置成了UTF-8!問題就在這裏。System.out.println已經把字符編碼成了GBK,而控制檯仍然以UTF-8的格式讀取字符,自然會出現亂碼。
  將控制檯的字符編碼設置爲GBK,亂碼問題解決。
  (這裏補充一點:eclipse的控制檯編碼是繼承了workspace的設置的,通常控制檯編碼裏沒有GBK的選項而且不能輸入。我們可以先在workspace的編碼設置中輸入GBK,然後在控制檯的設置中就可以看到GBK的選項了,設置好後再把workspace的字符編碼設置改回utf-8就是。)

    二、JSP文件中硬編碼中文字符,在瀏覽器上顯示亂碼。
    我們用eclipse編寫一個JSP頁面,使用tomcat瀏覽這個頁面時,整個頁面的中文字符都是亂碼。這是什麼原因呢?
    JSP頁面從編寫到在瀏覽器上瀏覽,總共有四次字符編解碼。
    1. 以某種字符編碼保存JSP文件
    2. Tomcat以指定編碼來讀取JSP文件並編譯
    3. Tomcat向瀏覽器以指定編碼來發送HTML內容
    4. 瀏覽器以指定編碼解析HTML內容
    這裏的四次字符編解碼,有一次發生錯誤最終顯示的就會是亂碼。我們依次來分析各次的字符編碼是如何設置的。
    - 保存JSP文件,這是在編輯器中設置的,比如eclipse中,設置文件字符類型爲utf-8。
    - JSP文件開頭的<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>,其中pageEncoding用來告訴tomcat此文件所用的字符編碼。這個編碼應該與eclipse保存文件用的編碼一致。Tomcat以此編碼方式來讀取JSP文件並編譯。
    - page標籤中的contentType用來設置tomcat往瀏覽器發送HTML內容所使用的編碼。這個編碼會在HTTP響應頭中指定以通知瀏覽器。
    - 瀏覽器根據HTTP響應頭中指定的字符編碼來解析HTML內容。如:
HTTP/1.1 200 OK
Date: Mon, 01 Sep 2008 23:13:31 GMT
Server: Apache/2.2.4 (Win32) mod_jk/1.2.26
Vary: Host,Accept-Encoding
Set-Cookie: JAVA2000_STYLE_ID=1; Domain=www.java2000.net; Expires=Thu, 03-Nov-2011 09:00:10 GMT; Path=/
Content-Encoding: gzip
Transfer-Encoding: chunked
Content-Type: text/html;charset=UTF-8
    另外,HTML中有個標籤<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">中也指定了charset。不過這個字符編碼只有在當網頁保存在本地作爲靜態網頁時有效,因爲沒有HTTP頭,所以瀏覽器根據此標籤來識別HTML內容的編碼方式。
   
    現在在JSP文件中硬編碼出現亂碼的機會比較小了,因爲大家都用瞭如eclipse的編輯器,基本上可以自動保證這幾個編碼設置的正確性。現在更多碰到的是在JSP文件中從其他數據源中讀取中文字符所產生的亂碼問題。
   
    三、在JSP文件中讀取字符文件並在頁面中顯示,中文字符顯示爲亂碼。
    比如,我們在JSP文件中使用以下代碼:
    <%
    BufferedReader reader = new BufferedReader(new FileReader("D://test.txt"));
  String content = reader.readLine();
  reader.close();
  %>
  <%=content%>
  test.txt裏保存的是中文字符,但在瀏覽器上看到的亂碼。這是個經常見到的問題。我們繼續用之前的方法一步步來分析輸入和輸出流
  1. test.txt是以某種編碼方式保存中文字符,比如UTF-8。
  2. BufferedReader直接讀取test.txt的字節內容並以默認方式構造字符串。分析BufferedReader的代碼,我們可以看到BufferedReader調用了FileReader的read方法,而FileReader又調用了FileInputStream的native的read方法。所謂native的方法,就是操作系統底層方法。那麼我們操作系統是中文系統,所以FileInputStream默認用GBK方式讀取文件。因爲我們保存test.txt用的是UTF-8,所以在這裏讀取文件內容使用GBK是錯誤的編碼。
  3. <%=content%>其實就是out.print(content),這裏又用到了HTTP的輸出流JspWriter,於是字符串content又被以JSP的page標籤中指定的UTF-8方式編碼成字節數組被髮送到瀏覽器端。
  4. 瀏覽器以HTTP頭中指定的方式解碼字符,這時無論是用GBK還是UTF-8解碼,顯示的都是亂碼。
  可見,我們字符編碼轉換在第二步時出錯了,UTF-8的字符串被當做GBK讀入了內存中。
  解決這個亂碼問題有兩種方法,一是把test.txt用GBK保存,則FileInputStream能正確讀入中文字符;二是使用InputStreamReader來轉換字符編碼,如:
  InputStreamReader sr = new InputStreamReader(new FileInputStream("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裏的中文參數後,在頁面顯示爲亂碼。
 
  在JAVA的WEB應用中,對request對象裏的parameters的中文處理一直是常見也最難搞的一隻大怪獸。經常是剛搞定了這邊,那邊又出了亂碼。而導致這種複雜性的,主要是此過程中字符編解碼次數非常多,而且無論是瀏覽器還是WEB服務器特別是TOMCAT總是不能給我們一個比較滿意的支持。
  首先我們來分析用GET方式上傳參數的亂碼情況。
  例如我們在瀏覽器地址欄輸入以下URL:http://localhost:8080/test/test.jsp?param=大家好
  我們的JSP代碼如此處理param這個參數:
  <% String text = request.getParameter("param");  %>
  <%=text%>
  而就這麼簡單的兩句代碼,我們很有可能在頁面上看到這樣的亂碼:´ó¼ÒºÃ
  網上對處理request.getParamter中的亂碼有很多文章和方法,也都是正確的,只是方法太多讓人一直不明白到底是爲什麼。這裏給大家分析一下到底是怎麼一回事。
  首先,我們來看看與request對象有哪些相關的編碼設置:
  1. JSP文件的字符編碼
  2. 請求這個帶參數URL的源頁面的字符編碼
  3. IE的高級設置中的選項“總以utf-8方式發送URL地址”
  4. TOMCAT的server.xml中配置URIEncoding
  5. 函數request.setCharacterEncoding()
  6. JS的encodeURIComponent函數與JAVA的URLDecoder類
  這麼多條相關編碼設置,也難怪大家被搞得頭暈了。這裏給大家根據各種情況給大家一一分析一下。見下表:

序號

請求源頁面編碼

從地址欄輸入URL訪問

TOMCAT的URIEncoding設置

IE的UTF-8發送URL地址設置

結果

1

UTF-8

 

未設置

打開

顯示符號亂碼

2

UTF-8

 

未設置

關閉

顯示符號亂碼

3

GBK

 

爲設置

打開

顯示符號亂碼

4

GBK

 

未設置

關閉

顯示符號亂碼

5

 

地址欄輸入

未設置

打開

顯示符號亂碼

6

 

地址欄輸入

未設置

關閉

顯示符號亂碼

7

UTF-8

 

GBK

打開

顯示漢字亂碼

8

UTF-8

 

GBK

關閉

顯示漢字亂碼

9

GBK

 

GBK

打開

正常

10

GBK

 

GBK

關閉

正常

11

 

地址欄輸入

GBK

打開

正常

12

 

地址欄輸入

GBK

關閉

正常

13

UTF-8

 

UTF-8

打開

IE6:奇數個的中文最後一位爲亂碼

IE7:正常

14

UTF-8

 

UTF-8

關閉

IE6:奇數個的中文最後一位爲亂碼

IE7:正常

15

 

地址欄輸入

UTF-8

打開

顯示口字亂碼

16

 

地址欄輸入

UTF-8

關閉

顯示口字亂碼

17

GBK

 

UTF-8

打開

顯示問號亂碼

18

GBK

 

UTF-8

關閉

顯示問號亂碼

19

 

地址欄輸入

UTF-8

打開

顯示口字亂碼

20

 

地址欄輸入

UTF-8

關閉

顯示口字亂碼


  以上表格里的現象,除了指名在IE7上,其他全是在IE6上測試的結果。
  由這個表我們可以看到,IE的“總以utf-8方式發送URL地址”設置並不影響對parameter的解析,而從頁面請求URL和從地址欄輸入URL居然也有不同的表現。
  根據這個表列出的現象,大家只要用smartSniff抓幾個網絡包,並稍稍調查一下TOMCAT的源代碼,就可以得出以下結論:
  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時發生的亂碼問題。不過個人覺得如果弄懂了整個字符編碼轉換的過程,基本上是用不到這種方法的。
   
  六、從數據庫中讀取中文字符數據,在頁面上顯示爲亂碼。
 
  對於數據庫中讀取中文字符出現亂碼的問題,本人遇到的還比較少,所以暫時沒有總結。如果大家有類似的經驗,歡迎補充說明,我一定註明作者身份。
 
  好了,對各種字符亂碼問題的分析就總結到這裏,相信只要把握“以指定編碼讀取--轉換爲UNICODE--以指定編碼輸入”這基本步驟,初學者也可以很快分析出字符亂碼的根源所在。另外我建議不要隨便使用new String(str.getBytes(enc1),enc2)這種方式來強行轉碼,也不要隨便使用網上的字符轉碼函數,我覺得只會把問題隱藏更深更複雜化。我們應該清晰地分析整個字符流的編解碼過程,自然可以找出亂碼的根源所在,從而保證整個字符流動中,在內存中的UNICODE始終是正確的。
 
 
  另外再附上個人總結的亂碼分析的一套祕籍!即從亂碼的長相來分析是哪種編碼轉換錯誤。有人可以聞香識酒,我們也來個看字識碼。請看下錶:

名稱

示例

特點

產生原因

古文碼

鐢辨湀瑕佸ソ濂藉涔犲ぉ澶╁悜涓?

大都爲不認識的古文,並加雜日韓文

以GBK方式讀取UTF-8編碼的中文

口字碼

����Ҫ�¨²�ѧϰ������

大部分字符爲小方塊

以UTF-8的方式讀取GBK編碼的中文

符號碼

由月要好好学习天天向上

大部分字符爲各種符號

以ISO8859-1方式讀取UTF-8編碼的中文

拼音碼

ÓÉÔÂÒªºÃºÃѧϰÌìÌìÏòÉÏ

大部分字符爲頭頂帶有各種類似聲調符號的字母

以ISO8859-1方式讀取GBK編碼的中文

問句碼

由月要好好學習天天向??

字符串長度爲偶數時正確,長度爲奇數時最後的字符變爲問號

以GBK方式讀取UTF-8編碼的中文,然後又用UTF-8的格式再次讀取

錕拷碼

錕斤拷錕斤拷要錕矯猴拷學習錕斤拷錕斤拷錕斤拷

全中文字符,且大部分字符爲“錕斤拷”這幾個字符

以UTF-8方式讀取GBK編碼的中文,然後又用GBK的格式再次讀取

 

  不過個人至今仍然弄不明白的就是問號碼的產生原因,問號碼即所有字符幾乎全部爲問號的亂碼。問號碼的出現有多種情況。我目前能確認的當我們把中文字符強行以ISO8859-1編碼寫入文件後,字符的高位信息會丟失,從而再次從文件中讀出字符時便全部變爲問號符。而我在JAVA代碼中用UTF-8的方式去讀取GBK編碼的字符,出來的也是問號碼,而並非口字碼,這是我百思不得其解的問題。


地址:http://hi.baidu.com/wjx_pl/blog/item/e7c345d1dda277c8572c84ab.html

因爲url傳送默認編碼是容器的編碼,tomcat默認是iso-8859-1.所以,request.gerParameter()獲得的值必須轉碼,除非設置tocmat的默認url編碼。  

這個是涉及到字符編碼的問題   一個字符在網頁間傳遞要經過編/解碼的問題   
  我來具體解釋一下這個語句username=new   String(username.getBytes("ISO8859_1"),"GBK");   
  網頁本身是gb2312(也就是gbk)對數據進行解碼的,那麼你要將這個數據轉換成ISO8859_1解碼   
  的數據,一定要先將這個數據編碼成gbk,然後通過getBytes()方法將其解碼成ISO8859_1編碼方式,那麼最後得到的數據纔是以ISO8859_1進行編碼的數據   

1、函數介紹
在Java中,字符串用統一的Unicode編碼,每個字符佔用兩個字節,與編碼有關的兩個主要函數爲:
1)將字符串用指定的編碼集合解析成字節數組,完成Unicode-〉charsetName轉換
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException 
2)將字節數組以指定的編碼集合構造成字符串,完成charsetName-〉Unicode轉換
public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
2、Unicode與各編碼之間的直接轉換
下面以對中文字符串"a中文"的編碼轉換爲例,來了解各種編碼之間的轉換
1)Unicode和GBK
測試結果如下,每個漢字轉換爲兩個字節,且是可逆的,即通過字節可以轉換回字符串
StringGBKByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4
ByteArrayGBKString:0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u4E2D/u6587(a中文)
2)Unicode和UTF-8
測試結果如下,每個漢字轉換爲三個字節,且是可逆的,即通過字節可以轉換回字符串
StringUTF-8ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87
ByteArrayUTF-8String:0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u4E2D/u6587(a中文)
3)Unicode和ISO-8859-1
測試結果如下,當存在漢字時轉換失敗,非可逆,即通過字節不能再轉換回字符串
StringISO-8859-1ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0x3F 0x3F
ByteArrayISO-8859-1String:0x61 0x3F 0x3F-〉/u0061/u003F/u003F(a??)
3、Unicode與各編碼之間的交叉轉換
在上面直接轉換中,由字符串(Unicode)生成的字節數組,在構造回字符串時,使用的是正確的編碼集合,如果使用的不是正確的編碼集合會怎樣呢?會正確構造嗎?如果不能正確構造能有辦法恢復嗎?會信息丟失嗎?
下面我們就來看看這種情況,這部分可以說明在某些情況下雖然我們最終正確顯示了結果,但其間仍然進行了不正確的轉換。
1)能夠正確顯示的中間不正確轉換
我們知道StringGBKByteArrayGBKString是正確的,但如果我們採用StringGBKByteArrayISO-8859-1String呢?通過測試結果如下:
StringGBKByteArrayISO-8859-1String:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u00D6/u00D0/u00CE/u00C4(a????)
這時我們得到的字符串爲?亂碼“a????”,但是通過繼續轉換我們仍然可以復原回正確的字符串“a中文”,過程如下:
StringGBKByteArrayISO-8859-1StringISO-8859-1ByteArrayGBKString
對應:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u00D6/u00D0/u00CE/u00C4(a????)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u4E2D/u6587(a中文)
也就是我們在首次構造字符串時,我們用了錯誤的編碼集合得到了錯誤的亂碼,但是我們通過錯上加錯,再用錯誤的編碼集合獲取字節數組,然後再用正確的編碼集合構造,就又恢復了正確的字符串。這時就屬於是“能夠正確顯示的中間不正確轉換”。在Jsp頁面提交數據處理時常常發生這種情況。
此外能夠正確顯示的中間不正確轉換還有:
StringUTF-8ByteArrayISO-8859-1StringISO-8859-1ByteArrayUTF-8String
StringUTF-8ByteArrayGBKStringGBKByteArrayUTF-8String
對應:/u0061/u4E2D/u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u6D93/uE15F/u6783(a涓枃)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u4E2D/u6587(a中文)
4、編碼過程中錯誤診斷參考
1)一個漢字對應一個問號
在通過ISO-8859-1從字符串獲取字節數組時,由於一個Unicode轉換成一個byte,當遇到不認識的Unicode時,轉換爲0x3F,這樣無論用哪種編碼構造時都會產生一個?亂碼。
2)一個漢字對應兩個問號
在通過GBK從字符串獲取字節數組時,由於一個Unicode轉換成兩個byte,如果此時用ISO-8859-1或用UTF-8構造字符串就會出現兩個問號。
若是通過ISO-8859-1構造可以再通過上面所說的錯上加錯恢復(即再通過從ISO-8859-1解析,用GBK構造);
若是通過UTF-8構造則會產生Unicode字符"/uFFFD",不能恢復,若再通過String-UTF-8〉ByteArray-GBK〉String,則會出現雜碼,如a錕斤拷錕斤拷
3)一個漢字對應三個問號
在通過UTF-8從字符串獲取字節數組時,由於一個
 

    這是java字符串處理的一個標準函數,其作用是將字符串所表示的字符按照charset編碼,並以字節方式表示。注意字符串在java內存中總是按unicode編碼存儲的。比如"中文",正常情況下(即沒有錯誤的時候)存儲爲"4e2d 6587",如果charset爲"gbk",則被編碼爲"d6d0 cec4",然後返回字節"d6 d0 ce c4".如果charset爲"utf8"則最後是"e4 b8 ad e6 96 87".如果是"iso8859-1",則由於無法編碼,最後返回 "3f 3f"(兩個問號)。

java   .class類的編碼爲:unicode;

windows 默認的編碼爲:中文:gb2312; 英文:iso8859;

String str = "張三" ;

byte[] jiema= str.getBytes("gb2312") ; //解碼

String   bianma = new String(jiema,"UTF-8");//編碼 如果上面的解碼不對 可能出現問題

2. new String(charset)

    這是java字符串處理的另一個標準函數,和上一個函數的作用相反,將字節數組按照charset編碼進行組合識別,最後轉換爲unicode存儲。參考上述getBytes的例子,"gbk" 和"utf8"都可以得出正確的結果"4e2d 6587",但iso8859-1最後變成了"003f 003f"(兩個問號)。

    因爲utf8可以用來表示/編碼所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

3. setCharacterEncoding()

    該函數用來設置http請求或者相應的編碼。

    對於request,是指提交內容的編碼,指定後可以通過getParameter()則直接獲得正確的字符串,如果不指定,則默認使用iso8859-1編碼,需要進一步處理。參見下述"表單輸入".值得注意的是在執行setCharacterEncoding()之前,不能執行任何getParameter()。java doc上說明:This method must be called prior to reading request parameters or reading input using getReader()。而且,該指定只對POST方法有效,對GET方法無效。分析原因,應該是在執行第一個getParameter()的時候,java將會按照編碼分析所有的提交內容,而後續的getParameter()不再進行分析,所以setCharacterEncoding()無效。而對於GET方法提交表單是,提交的內容在URL中,一開始就已經按照編碼分析所有的提交內容,setCharacterEncoding()自然就無效。

    對於response,則是指定輸出內容的編碼,同時,該設置會傳遞給瀏覽器,告訴瀏覽器輸出內容所採用的編碼。

4. 處理過程

    下面分析兩個有代表性的例子,說明java對編碼有關問題的處理方法。

   4.1. 表單輸入

    User input *(gbk:d6d0 cec4) browser *(gbk:d6d0 cec4) web server iso8859-1(00d6 00d 000ce 00c4) class,需要在class中進行處理:getbytes("iso8859-1")爲d6 d0 ce c4,new String("gbk")爲d6d0 cec4,內存中以unicode編碼則爲4e2d 6587.

    l 用戶輸入的編碼方式和頁面指定的編碼有關,也和用戶的操作系統有關,所以是不確定的,上例以gbk爲例。

    l 從browser到web server,可以在表單中指定提交內容時使用的字符集,否則會使用頁面指定的編碼。而如果在url中直接用?的方式輸入參數,則其編碼往往是操作系統本身的編碼,因爲這時和頁面無關。上述仍舊以gbk編碼爲例。

    l Web server接收到的是字節流,默認時(getParameter)會以iso8859-1編碼處理之,結果是不正確的,所以需要進行處理。但如果預先設置了編碼(通過request. setCharacterEncoding ()),則能夠直接獲取到正確的結果。

    l 在頁面中指定編碼是個好習慣,否則可能失去控制,無法指定正確的編碼。

    4.2. 文件編譯

    假設文件是gbk編碼保存的,而編譯有兩種編碼選擇:gbk或者iso8859-1,前者是中文windows的默認編碼,後者是linux的默認編碼,當然也可以在編譯時指定編碼。

    Jsp *(gbk:d6d0 cec4) java file *(gbk:d6d0 cec4) compiler read uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) compiler write utf(gbk: e4b8ad e69687; iso8859-1: *) compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) class.所以用gbk編碼保存,而用iso8859-1編譯的結果是不正確的。

    class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser.

    l 文件可以以多種編碼方式保存,中文windows下,默認爲ansi/gbk.

    l 編譯器讀取文件時,需要得到文件的編碼,如果未指定,則使用系統默認編碼。一般class文件,是以系統默認編碼保存的,所以編譯不會出問題,但對於jsp文件,如果在中文windows下編輯保存,而部署在英文linux下運行/編譯,則會出現問題。所以需要在jsp文件中用pageEncoding指定編碼。

    l Java編譯的時候會轉換成統一的unicode編碼處理,最後保存的時候再轉換爲utf編碼。

    l 當系統輸出字符的時候,會按指定編碼輸出,對於中文windows下,System.out將使用gbk編碼,而對於response(瀏覽器),則使用jsp文件頭指定的contentType,或者可以直接爲response指定編碼。同時,會告訴browser網頁的編碼。如果未指定,則會使用iso8859-1編碼。對於中文,應該爲browser指定輸出字符串的編碼。

    l browser顯示網頁的時候,首先使用response中指定的編碼(jsp文件頭指定的contentType最終也反映在response上),如果未指定,則會使用網頁中meta項指定中的contentType.

5. 幾處設置

    對於web應用程序,和編碼有關的設置或者函數如下。

    5.1. jsp編譯

    指定文件的存儲編碼,很明顯,該設置應該置於文件的開頭。例如:。另外,對於一般class文件,可以在編譯的時候指定編碼。

    5.2. jsp輸出

    指定文件輸出到browser是使用的編碼,該設置也應該置於文件的開頭。例如:。該設置和response.setCharacterEncoding("GBK")等效。

    5.3. meta設置

    指定網頁使用的編碼,該設置對靜態網頁尤其有作用。因爲靜態網頁無法採用jsp的設置,而且也無法執行response.setCharacterEncoding()。例如:

    如果同時採用了jsp輸出和meta設置兩種編碼指定方式,則jsp指定的優先。因爲jsp指定的直接體現在response中。

    需要注意的是,apache有一個設置可以給無編碼指定的網頁指定編碼,該指定等同於jsp的編碼指定方式,所以會覆蓋靜態網頁中的meta指定。所以有人建議關閉該設置。

   5.4. form設置

    當瀏覽器提交表單的時候,可以指定相應的編碼。例如:。一般不必不使用該設置,瀏覽器會直接使用網頁的編碼。



發佈了97 篇原創文章 · 獲贊 3 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章