Java編碼問題總結

編碼與解碼

電腦只能處理011001這樣的二進制數字,字符是日常生活中我們使用的符號,爲了電腦能夠存儲、傳輸和展示字符,所以,我們需要把字符轉換爲0110000這樣的二進制碼。這就是所謂編碼。相反,把011000這樣的二進制碼轉換爲字符的過程就是解碼!JAVA裏,char表示一個字符,String表示字符串!

 

具體把哪個字符映射到哪個二進制串上,是由國家(國家標準)、國際組織(國際標準)等決定的!

 

一般不用二進制串來表示某個字符的編碼(因爲寫起來、閱讀起來都很麻煩),所以一般是用十六進制的串來表示某個字符的編碼。

 

因此:

字符 <--> 十六進制串,之間的映射的集合(因爲有很多字符),就構成了字符集。

 

你經常見到的字符集是:UnicodeUTF-8GB2312GB18030GBKBig-5ISO-8859-1(又名:Latin-1

 

UnicodeUTF-8能夠支持目前世界上所有語言文字的字符

GB2312是舊的國家標準,不支持繁體漢字

GBK不是國家標準,但支持繁體漢字

GB18030是新的國家標準,也支持繁體漢字

Big-5,能支持繁體漢字

ISO-8859-1,不支持漢字,英美等拉丁語系的國家常用這個編碼

 

舉例:

public class EncodingTest01 {

    public static void main(String[] args) throws Exception{

       String s = "中國";

       byte[] bs = s.getBytes("GB18030");

       System.out.println(Utils.byteArrayToHex(bs));

    }

}

public class Utils {

    private static char[] hexDigits = {

       '0','1','2','3','4','5','6','7',

       '8','9','A','B','C','D','E','F'

    };

    public static String byteArrayToHex(byte[] bs){

       StringBuffer sb = new StringBuffer();

       for(byte b:bs){

           sb.append(hexDigits[(b >> 4) & 0x0000000F]);

           sb.append(hexDigits[b & 0x0000000F]);

           sb.append(",");

       }

       return sb.toString();

    }

}

 

“中國”,這兩個簡體漢字,在不同的字符集中,用上述程序輸出,其編碼分別爲:

Unicode – FE,FF,4E,2D,56,FD,

UTF-8 - E4,B8,AD,E5,9B,BD

GB18030 - D6,D0,B9,FA,

ISO-8859-1 - 3F,3F,

 

解釋:

關於Unicode

Unicode編碼之後的“中國”,其編碼的前面有FE,FF兩個額外的字節。這兩個字節稱爲BOMByte Order Mark)。“中”這個漢字,它的Unicode編碼是:4E,2D,那麼在傳輸的過程中,是把4E放在前面,還是把2D放在前面呢?這有BOM來決定。如果BOMFEFF(稱爲Big Endian),表示4E在前;如果BOMFFFE(稱爲Little Endian),表示2D在前。

 

也就是說,如果你的文件編碼是Unicode(也可以叫做UTF-16),那麼文件開頭的字節就是:FEFF(以這種方式開頭的編碼叫UTF-16BE)或FFFE(以這種方式開頭的編碼叫做UTF-16LE

 

UTF-16BEUTF-16Unicode是一樣的!

 

關於UTF-8

UTF-8編碼的文件,其文件頭也有一段標識:EF BB BF

 

其它編碼的文件,其文件頭沒有什麼標識!!!

 

對於使用ISO-8859-1編碼方式來對“中國”二字進行編碼之後,得到的是:3F 3F,這是因爲ISO-8859-1字符集中根本就沒有“中國”這兩個字符!所以,它用3F來代替!

 

解碼

解碼,實際上就是把二進制流(也就是字節流,即字節數組)轉換爲字符。字節數組是什麼樣的編碼(即用哪個字符集對它編碼),你必需使用相同的字符集來解碼

 

public class EncodingTest02 {

    public static void main(String[] args) throws Exception{

       byte[] bs = {(byte)0xD6,(byte)0xD0,(byte)0xB9,(byte)0xFA};

       String s = new String(bs,"GB18030");

       System.out.println(s);

    }

}

 

像上述程序所示的那樣,我們知道:D6D0 B9FA 是“中國”兩個字符的“GB18030”編碼,所以,你可以使用這個字符集來把其對應的字節數組解碼爲字符!

 

Java中的字符

你首先要理解的是,JAVA可以把字符存儲在內存中(即也是以0110010這樣的方式存儲在內存中)!那麼,JAVA把字符存儲在內存中的時候,它使用的是什麼編碼呢?答案是:Unicode

 

因此,我們知道,“中國”,這兩個漢字,在內存中,是以“4E,2D,56,FD”這種方式存在的!

 

不過要注意,JAVAclass文件,是以UTF-8方式編碼的!JAVA虛擬機讀取class文件的時候,通過UTF-8編碼把class文件讀入內存,並轉換爲UTF-16(即Unicode)。

 

因此,new String(byte[],“GB18030“)的真正意思是:把字節流(它是以GB18030方式編碼的)轉換爲Unicode的字節流存在內存中!

 

 

如上圖所示,假設有“中國”兩個漢字,通過getBytes(“encoding”)能將其轉換爲不同的編碼!

 

再看下圖,假設有一個字節流,現在想把它轉換爲字符串:

 

 

一個本來是UTF-8編碼的字節流,如果你把它當成ISO-8859-1進行轉換,ISO-8859-1的規則是每個字節一個字符,所以得到了長度爲6的字符串,如果你將它輸出,將是亂碼!現在你可以反向轉換,通過ISO-8859-1編碼得到它的字節流,這將原封不動得到一個字節流,然後,你又可以對這個字節流進行正確的解碼了!

 

簡單總結一下

如果你拿到一個字節流,你需要清楚的知道,你這個字節流是用哪個字符集進行編碼的!

 

如果你拿到一個字符串,你發現它輸出的時候是亂碼,那麼,你究竟能不能通過某種技術手段解決這個亂碼問題呢?答案是:不一定!

l           因爲你拿到了一個字符串,所以,可以肯定,你拿到的字符串是由於某人將它按照某種字符集將字節流進行了解碼而得到的字符串!

a)         假如某人是按照ISO-8859-1字符集對字節流進行解碼而得到的字符串,那麼,恭喜你,你將可以通過某種技術手段,把你手中的亂碼字符串轉換爲正確的字符串!這種技術手段就是:先getBytes(ISO-8859-1)得到一個字節流,然後把這個字節流用new String(byte[],“正確的字符集”)轉換爲正確的字符串即可!

b)        假如某人不是按照ISO-8859-1字符集對字節流進行解碼的,那麼,很不幸,有很大的可能是你沒有辦法將它再正確地還原了!

 

頁面編碼

通過GET方法向後臺遞交的請求

向服務器遞交的請求中,字符也是需要進行編碼之後傳輸的!瀏覽器將會自動對URL地址中的漢字進行編碼之後傳輸。如果你直接在瀏覽器地址欄輸入URL地址及漢字,那麼瀏覽器將自動使用當前操作系統的默認字符集進行編碼(中文操作系統就是GB2312)之後傳輸到服務器;如果你在某個頁面上點擊一個鏈接(此鏈接後面附帶着中文漢字),那麼,瀏覽器將自動根據這個頁面所使用的字符集來對漢字進行編碼之後,傳輸到服務器!

 

比如“中國”兩個漢字,假如頁面編碼是GB18030(或GBKGB2312),那麼向後傳輸的串就是:%D6%D0%B9%FA

 

比如:http://localhost:8080/myservlet/AddUser?username=%D6%D0%B9%FA

 

我們後臺使用request.getParameter(username)來得到一個字符串,結果發現是亂碼,那麼可以肯定,某人幫我們將從客戶端傳輸過來的字節流按照某種字符集解碼成了字符串!某人就是應用服務器(TOMCAT),某種字符集就是應用服務器的默認字符集,是ISO-8859-1。所以,很幸運,還有救!

       String username = request.getParameter("username");

      

       //將它轉換爲正確的字符串!

       byte[] bs = username.getBytes("ISO-8859-1");

       username = new String(bs,"GB18030");

      

       //正確

       System.out.println(username);

 

當然,爲了避免每次都這樣幹,我們乾脆修改TOMCAT對於URL編碼的默認解碼字符集即可!

修改server.xml文件中的下面的語句,加上URIEncoding=“GB18030“的配置即可:

    <Connector port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

               redirectPort="8443" URIEncoding="GB18030"/>

 

通過POST向後臺遞交的請求

瀏覽器將按照表單所在的頁面所使用的字符集對數據進行編碼之後傳輸!

 

TOMCAT中配置的URIEncoding只對GET方式遞交的請求中的數據有效而對於POST請求你必須在調用request.getParameter(“xxx”)之前,調用:request.setCharacterEncoding("GB18030");

 

Response的編碼

 

在調用response.getWriter()之前,調用response.setCharacterEncoding("GB18030")

即可!

 

或者:

在設置ContentType的時候設置也可以:

response.setContentType("text/html;charset=GB18030");

 

 

 

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