1. 程式人生 > >《深入分析Java Web技術內幕》讀後感之JAVA WEB 中文亂碼問題

《深入分析Java Web技術內幕》讀後感之JAVA WEB 中文亂碼問題

為什麼要編碼

在計算機中儲存資訊的最小單元是1個位元組(8bit),所以能表示的字元範圍是0-255個。人類要表達的字元太多,無法用1個位元組完全表示。要解決這個問題需要使用新的資料結構char,從char到byte必須編碼。

編碼格式

ASCII碼:共128個,用一個位元組的低7位表示,0-31控制字元,32-126列印字元。

ISO-8859-1:拓展自ASCII碼,覆蓋大多數西歐語言字元,單位元組編碼,共能表示256個位元組。

GB2312:雙位元組編碼,包含6763個漢字。

GBK:拓展自GB2312,和GB2312相容,能表示21003個漢字。

GB18030:可能單位元組、雙位元組或四位元組,應用不廣泛。

UTF-16:具體定義了Unicode字元在計算機中的存取方法。定長,使用兩個位元組表示任何字元。Java以UTF-16作為記憶體的字元儲存格式。

UTF-8:採用變長技術,每個編碼區域有不同的字碼長度。中文一般佔三個位元組。

Java中的編碼操作

I/O操作:

InputStreamReader類是關聯位元組到字元的橋樑,負責在I/O過程中處理讀取位元組到字元的轉換,其委託StreamDecoder實現位元組到字元的節碼實現,解碼過程中必須由使用者指定Charset編碼格式,預設使用本地環境中的預設字符集(中文環境即為GBK)。

OutputStreamWriter負責轉換字元到位元組,編碼格式和預設編碼規則與解碼一致。

建議不使用作業系統的預設編碼,這樣會使應用程式的編碼格式和執行環境繫結起來,在跨環境會出現亂碼問題。

@Test
    public void testCopyFile(){
        try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\Test.txt"),"UTF-8")); 
         BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\Test_.txt"),"UTF-8")); )
       {
            String str = null;
            int i = 0;
            while((str = reader.readLine()) != null){
                if (i != 0)
                    writer.write("\r\n");
                i++;
                writer.write(str);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }  
    }

記憶體操作:

String str = "中文字串";
String newStr = new String(str.getBytes(),"UTF-8");
String string = "中文字串";
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode(string);   
CharBuffer charBuffer = charset.decode(byteBuffer); 

補充:

  • String → ByteBuffer:Charset.encode()
  • ByteBuffer → String:Charset.decode().toString()
  • CharBuffer→ String :toString()
  • ByteBuffer → byte[]:array()
  • byte[] → ByteBuffer :ByteBuffer.wrap()
  • CharBuffer  → char[]:array()
  • char[] → CharBuffer:CharBuffer.wrap()

對幾種編碼格式的比較

UTF-16編碼效率較高,從字元到位元組的相互轉換更加簡單,適合本地磁碟和記憶體之間使用,可進行字元和位元組的快速切換,但是不適合網路之間傳輸。其採用順序編碼,不能對單個字元的編碼進行校驗,如果中間的一個字元碼值損壞,後面所有碼值都受影響,相較而言UTF-8更適合網路傳輸。

UTF-8編碼與GBK和GB2312不同,不用查碼錶,所以UTF-8的編碼效率更高,所以在儲存中文字元時採用UTF-8編碼比較理想。UTF-8中單個字元損壞不會影響後面的其他字元,編碼效率介於GBK和UTF-16之間,是理想的中文編碼方式。

JavaWeb中設計的編解碼

1.資料經過網路傳輸時都是以位元組為單位,所有的資料都必須能夠被序列化為位元組。在Java中資料要被序列化,必須繼承Serializable介面。

2.把整型數字1234567當作字元來儲存,則採用UTF-8編碼將會佔用7個位元組,採用UTF-16編碼則會佔用14個位元組,但當作int型別的數字只需要4個位元組。所以,只看字元本身的長度是沒有意義的,即使一樣的字元,採用不同的編碼最終儲存的大小也會不同,所以從字元到位元組要看編碼型別。

 URL的編解碼

對URL的URI部分進行解碼的字符集在<Connector URIEncoding="UTF-8"/>中定義,若沒有定義則以預設編碼ISO-8859-1解析。有中文URL時最好把URIEncoding設定為UTF-8編碼。

對於QueryString的解析:QueryString的解碼字符集要麼是Header中ContentType定義的Charset,要麼預設是ISO-8859-1要使用ContentType中定義的編碼,就要設定useBodyEncodingForURI:<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>。這個配置項不是對整個URI都採用BodyEncoding編碼,僅僅是對QueryString使用BodyEncoding解碼。

HTTP Header編解碼

客戶端發起的HTTP請求除了URL外,還可能會在Header中傳遞其他引數(如Cookie)。對Header中的項進行解碼預設使用ISO-8859-1,且不能設定Header其他的解碼格式,若設定的Header中有非ASCII字元,解碼中肯定會出現亂碼。若一定要傳遞,則呼叫Tomcat中的URLEncoder編碼,再新增到Header中。

POST表單的編解碼

POST表單的引數傳遞方式是通過HTTP的BODY傳遞到伺服器的,當提交時先根據ContentType中的字符集進行解碼,字符集編碼可以由request.setCharacterEncoding(charset)來設定。

此外務必注意:對POST表單提交引數的解碼是發生在getParameter時,所以在第一次呼叫request.getParameter方法之前就要先設定request.setCharacterEncoding(charset)方法。

關於上傳的檔案編碼:也是使用ContentType定義的字符集編碼,不過上傳檔案是以位元組流的方式傳輸到伺服器的本地臨時目錄,此過程尚不涉及字元編碼,只有當檔案內容新增到parameters時才進行編碼。

HTTP BODY的編解碼

編解碼字符集通過response.setCharacterEncoding來設定,通過Header的Content-Type返回客戶端。若Header中沒有Content-Type,瀏覽器會根據<meta http-equiv="Content-Type" content="text/html; charset=utf-8">中的charset來解碼,若依然沒有該屬性,瀏覽器則使用預設編碼。

補充:使用JDBC來存取資料時要和資料的內建編碼保持一致,可以設定JDBC URL來指定:jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8