java中文GBK和UTF-8編碼轉換亂碼的分析

 

原文:http://blog.csdn.net/54powerman/article/details/77575656java

做者:54powerman安全

一直覺得,java中任意unicode字符串,能夠使用任意字符集轉爲byte[]再轉回來,只要不拋出異常就不會丟失數據,事實證實這是錯的。app

通過這個實例,也明白了爲何 getBytes()須要捕獲異常,雖然有時候它也沒有捕獲到異常。測試

言歸正傳,先看一個實例。編碼

用ISO-8859-1中轉UTF-8數據spa

設想一個場景:.net

用戶A,有一個UTF-8編碼的字節流,經過一個接口傳遞給用戶B;code

用戶B並不知道是什麼字符集,他用ISO-8859-1來接收,保存;blog

在必定的處理流程處理後,把這個字節流交給用戶C或者交還給用戶A,他們都知道這是UTF-8,他們解碼獲得的數據,不會丟失。接口

下面代碼驗證:

  1 public static void main(String[] args) throws Exception {
  2  	  //這是一個unicode字符串,與字符集無關
  3  	  String str1 = "用戶";
  4 
  5  	  System.out.println("unicode字符串:"+str1);
  6 
  7  	  //將str轉爲UTF-8字節流
  8  	  byte[] byteArray1=str1.getBytes("UTF-8");//這個很安全,UTF-8不會形成數據丟失
  9 
 10  	  System.out.println(byteArray1.length);//打印6,沒毛病
 11 
 12  	  //下面交給另一我的,他不知道這是UTF-8字節流,所以他當作ISO-8859-1處理
 13 
 14  	  //將byteArray1當作一個普通的字節流,按照ISO-8859-1解碼爲一個unicode字符串
 15  	  String str2=new String(byteArray1,"ISO-8859-1");
 16 
 17  	  System.out.println("轉成ISO-8859-1會亂碼:"+str2);
 18 
 19  	  //將ISO-8859-1編碼的unicode字符串轉回爲byte[]
 20  	  byte[] byteArray2=str2.getBytes("ISO-8859-1");//不會丟失數據
 21 
 22  	  //將字節流從新交回給用戶A
 23 
 24  	  //從新用UTF-8解碼
 25  	  String str3=new String(byteArray2,"UTF-8");
 26 
 27  	  System.out.println("數據沒有丟失:"+str3);
 28  	}
 29 輸出:
 30 
 31  	unicode字符串:用戶
 32  	6
 33  	轉成ISO-8859-1會亂碼:ç」¨æˆ·
 34  	數據沒有丟失:用戶

用GBK中轉UTF-8數據

重複前面的流程,將ISO-8859-1 用GBK替換。

只把中間一段改掉:

  1 //將byteArray1當作一個普通的字節流,按照GBK解碼爲一個unicode字符串
  2  	    String str2=new String(byteArray1,"GBK");
  3 
  4  	    System.out.println("轉成GBK會亂碼:"+str2);
  5 
  6  	    //將GBK編碼的unicode字符串轉回爲byte[]
  7  	    byte[] byteArray2=str2.getBytes("GBK");//數據會不會丟失呢?
  8 運行結果:
  9 
 10  	unicode字符串:用戶
 11  	6
 12  	轉成GBK會亂碼:鐢ㄦ埛
 13  	數據沒有丟失:用戶

好像沒有問題,這就是一個誤區。

修改原文字符串從新測試

將兩個漢字 「用戶」 修改成三個漢字 「用戶名」 從新測試。

ISO-8859-1測試結果:

  1 unicode字符串:用戶名
  2  	9
  3  	轉成GBK會亂碼:ç」¨æˆ·å
  4  	數據沒有丟失:用戶名
  5 GBK 測試結果:
  6 
  7  	unicode字符串:用戶名
  8  	9
  9  	轉成GBK會亂碼:鐢ㄦ埛鍚�
 10  	數據沒有丟失:用戶�?

結論出來了

ISO-8859-1 能夠做爲中間編碼,不會致使數據丟失;

GBK 若是漢字數量爲偶數,不會丟失數據,若是漢字數量爲奇數,一定會丟失數據。

why?

爲何奇數個漢字GBK會出錯

直接對比兩種字符集和奇偶字數的情形

從新封裝一下前面的邏輯,寫一段代碼來分析:

  1 public static void demo(String str) throws Exception {
  2  	  System.out.println("原文:" + str);
  3 
  4  	  byte[] utfByte = str.getBytes("UTF-8");
  5  	  System.out.print("utf Byte:");
  6  	  printHex(utfByte);
  7  	  String gbk = new String(utfByte, "GBK");//這裏實際上把數據破壞了
  8  	  System.out.println("to GBK:" + gbk);
  9 
 10  	  byte[] gbkByte=gbk.getBytes("GBK");
 11  	  String utf = new String(gbkByte, "UTF-8");
 12  	  System.out.print("gbk Byte:");
 13  	  printHex(gbkByte);
 14  	  System.out.println("revert UTF8:" + utf);
 15  	  System.out.println("===");
 16  	//      若是gbk變成iso-8859-1就沒問題
 17  	}
 18 
 19  	public static void printHex(byte[] byteArray) {
 20  	  StringBuffer sb = new StringBuffer();
 21  	  for (byte b : byteArray) {
 22  	    sb.append(Integer.toHexString((b >> 4) & 0xF));
 23  	    sb.append(Integer.toHexString(b & 0xF));
 24  	    sb.append("");
 25  	  }
 26  	  System.out.println(sb.toString());
 27  	};
 28 
 29  	public static void main(String[] args) throws Exception {
 30  	  String str1 = "姓名";
 31  	  String str2 = "用戶名";
 32  	  demo(str1,"UTF-8","ISO-8859-1");
 33  	  demo(str2,"UTF-8","ISO-8859-1");
 34 
 35  	  demo(str1,"UTF-8","GBK");
 36  	  demo(str2,"UTF-8","GBK");
 37  	}
 38 輸出結果:
 39 
 40  	原文:姓名
 41  	UTF-8 Byte:e5 a7 93 e5 90 8d
 42  	to ISO-8859-1:å§「名
 43  	ISO-8859-1 Byte:e5 a7 93 e5 90 8d
 44  	revert UTF-8:姓名
 45  	===
 46  	原文:用戶名
 47  	UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
 48  	to ISO-8859-1:ç」¨æˆ·å
 49  	ISO-8859-1 Byte:e7 94 a8 e6 88 b7 e5 90 8d
 50  	revert UTF-8:用戶名
 51  	===
 52  	原文:姓名
 53  	UTF-8 Byte:e5 a7 93 e5 90 8d
 54  	to GBK:濮撳悕
 55  	GBK Byte:e5 a7 93 e5 90 8d
 56  	revert UTF-8:姓名
 57  	===
 58  	原文:用戶名
 59  	UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
 60  	to GBK:鐢ㄦ埛鍚�
 61  	GBK Byte:e7 94 a8 e6 88 b7 e5 90 3f
 62  	revert UTF-8:用戶�?
 63  	===

爲何GBK會出錯

前三段都沒問題,最後一段,奇數個漢字的utf-8字節流轉成GBK字符串,再轉回來,前面一切正常,最後一個字節,變成了 「0x3f」,即」?」

咱們使用」用戶名」 三個字來分析,它的UTF-8 的字節流爲:

[e7 94 a8] [e6 88 b7] [e5 90 8d]

咱們按照三個字節一組分組,他被用戶A當作一個總體交給用戶B。

用戶B因爲不知道是什麼字符集,他當作GBK處理,由於GBK是雙字節編碼,以下按照兩兩一組進行分組:

[e7 94] [a8 e6] [88 b7] [e5 90] [8d ?]

不夠了,怎麼辦?它把 0x8d當作一個未知字符,用一個半角Ascii字符的 「?」 代替,變成了:

[e7 94] [a8 e6] [88 b7] [e5 90] 3f

數據被破壞了。

爲何 ISO-8859-1 沒問題

由於 ISO-8859-1 是單字節編碼,所以它的分組方案是:

[e7] [94] [a8] [e6] [88] [b7] [e5] [90] [8d]

所以中間不作任何操做,交回個用戶A的時候,數據沒有變化。

關於Unicode編碼

由於UTF-16 區分大小端,嚴格講:unicode==UTF16BE。

  1 public static void main(String[] args) throws Exception {
  2  	  String str="測試";
  3  	  printHex(str.getBytes("UNICODE"));
  4  	  printHex(str.getBytes("UTF-16LE"));
  5  	  printHex(str.getBytes("UTF-16BE"));
  6  	}
  7 運行結果:
  8 
  9  	fe ff 6d 4b 8b d5
 10  	4b 6d d5 8b
 11  	6d 4b 8b d5

其中 「fe ff」 爲大端消息頭,同理,小端消息頭爲 「ff fe」。

小結

做爲中間轉存方案,ISO-8859-1 是安全的。

UTF-8 字節流,用GBK字符集中轉是不安全的;反過來也是一樣的道理。

  1 byte[] utfByte = str.getBytes("UTF-8");
  2  	String gbk = new String(utfByte, "GBK");
  3  	這是錯誤的用法,雖然在ISO-8859-1時並沒報錯。
  4 
  5  	首先,byte[] utfByte = str.getBytes("UTF-8");
  6  	執行完成以後,utfByte 已經很明確,這是utf-8格式的字節流;
  7 
  8  	而後,gbk = new String(utfByte, "GBK"),
  9  	對utf-8的字節流使用gbk解碼,這是不合規矩的。
 10 
 11  	就比如一個美國人說一段英語,讓一個不懂英文又不會學舌的日本人聽,而後傳遞消息給另外一個美國人。
 12 
 13  	爲何ISO-8859-1 沒問題呢?
 14 
 15  	由於它只認識一個一個的字節,就至關因而一個錄音機。我管你說的什麼鬼話連篇,過去直接播放就能夠了。
 16 getBytes() 是會丟失數據的操做,並且不必定會拋異常。
 17 
 18 unicode是安全的,由於他是java使用的標準類型,跨平臺無差別。
相關文章
相關標籤/搜索