關於JAVA字符編碼:Unicode,ISO-8859-1,GBK,UTF-8編碼及相互轉換

由於url傳送默認編碼是容器的編碼,tomcat默認是iso-8859-1.因此,request.gerParameter()得到的值必須轉碼,除非設置tocmat的默認url編碼。  java

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

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
測試結果以下,每一個漢字轉換爲兩個字節,且是可逆的,即經過字節能夠轉換回字符串
String GBK ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4
ByteArray GBK String:0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u4E2D/u6587(a中文)
 
2)Unicode和UTF-8
測試結果以下,每一個漢字轉換爲三個字節,且是可逆的,即經過字節能夠轉換回字符串
String UTF-8 ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87
ByteArray UTF-8 String:0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u4E2D/u6587(a中文)
3)Unicode和ISO-8859-1
測試結果以下,當存在漢字時轉換失敗,非可逆,即經過字節不能再轉換回字符串
String ISO-8859-1 ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0x3F 0x3F
ByteArray ISO-8859-1 String:0x61 0x3F 0x3F-〉/u0061/u003F/u003F(a??)
 
3、Unicode與各編碼之間的交叉轉換
在上面直接轉換中,由字符串(Unicode)生成的字節數組,在構造回字符串時,使用的是正確的編碼集合,若是使用的不是正確的編碼集合會怎樣呢?會正確構造嗎?若是不能正確構造能有辦法恢復嗎?會信息丟失嗎?
 
下面咱們就來看看這種狀況,這部分能夠說明在某些狀況下雖然咱們最終正確顯示告終果,但其間仍然進行了不正確的轉換。
 
1)可以正確顯示的中間不正確轉換
咱們知道String GBK ByteArray GBK String是正確的,但若是咱們採用String GBK ByteArray ISO-8859-1 String呢?經過測試結果以下:
String GBK ByteArray ISO-8859-1 String:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u00D6/u00D0/u00CE/u00C4(a????)
 
這時咱們獲得的字符串爲?亂碼「a????」,可是經過繼續轉換咱們仍然能夠復原回正確的字符串「a中文」,過程以下:
String GBK ByteArray ISO-8859-1 String ISO-8859-1 ByteArray GBK String
對應:/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頁面提交數據處理時經常發生這種狀況。
 
此外可以正確顯示的中間不正確轉換還有:
String UTF-8 ByteArray ISO-8859-1 String ISO-8859-1 ByteArray UTF-8 String
String UTF-8 ByteArray GBK String GBK ByteArray UTF-8 String
對應:/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"(兩個問號)。web

java   .class類的編碼爲:unicode;apache

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

String str = "張三" ;數組

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

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

2. new String(charset)jsp

    這是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設置

    當瀏覽器提交表單的時候,能夠指定相應的編碼。例如:。通常沒必要不使用該設置,瀏覽器會直接使用網頁的編碼。

按照字節數截取字符串:

package com.example.web.filter;


public class Test {
    public static void main(String[] args) throws Exception {
//        String ss=".abcdefghigklmnopqrstuvwxyABCDEFGHIJKLMNOPQRSTUVWXYZ";
//        for (int i = 0; i < ss.getBytes().length; i++) {
//            System.out.println(ss.getBytes()[i]);
//        }
        
        System.out.println("是".getBytes().length);
        String str = "1是1否是字節231we撒rw";
        System.out.println(str.getBytes().length);
        String strgbk = new String(str.getBytes("GBK"),"GBK");
        System.out.println(strgbk);
        int num=trimGBK(strgbk.getBytes(),11);
        System.out.println(strgbk.substring(0,num));
    
        System.out.println("----------");
        
        num=trimUTF8(str.getBytes(),11);
        System.out.println(str.substring(0,num));
        
    }

    private static int trimGBK(byte[] buf, int n) {
        int num=0;
        boolean firstHalf = false;
        for (int i = 0; i < n ; i++) {
            if (buf[i]<0 && !firstHalf) {
                firstHalf=true;
            }else {
                num++;
                firstHalf=false;
            }
        }
        return num;
    }
    /**
     * 若是是UTF8編碼,它佔三個字節,每一個字節都小於0
     * @param buf 字節數組
     * @param n 須要截取的字符串字節長度
     * @return
     */
    private static int trimUTF8(byte[] buf, int n) {
        int num=0;
        int count=1;
        for (int i = 0; i < n ; i++) {
            if (buf[i]<0 && count!=3) {
                count++;
            }else {
                num++;
                count=1;
            }
        }
        return num;
    }
    
}
相關文章
相關標籤/搜索