再谈java乱码:GBK和UTF-8互转尾部乱码问题分析
一直以為java中任意unicode字符串可以使用任意字符集轉為byte[]再轉回來只要不拋出異常就不會丟失數據事實證明這是錯的。
經過這個實例也明白了為什么 getBytes()需要捕獲異常雖然有時候它也沒有捕獲到異常。
言歸正傳先看一個實例。
用ISO-8859-1中轉UTF-8數據
設想一個場景
用戶A有一個UTF-8編碼的字節流通過一個接口傳遞給用戶B
用戶B并不知道是什么字符集他用ISO-8859-1來接收保存
在一定的處理流程處理后把這個字節流交給用戶C或者交還給用戶A他們都知道這是UTF-8他們解碼得到的數據不會丟失。
下面代碼驗證
public static void main(String[] args) throws Exception {//這是一個unicode字符串與字符集無關String str1 = "用戶";System.out.println("unicode字符串"+str1);//將str轉為UTF-8字節流byte[] byteArray1=str1.getBytes("UTF-8");//這個很安全UTF-8不會造成數據丟失System.out.println(byteArray1.length);//打印6沒毛病//下面交給另外一個人他不知道這是UTF-8字節流因此他當做ISO-8859-1處理//將byteArray1當做一個普通的字節流按照ISO-8859-1解碼為一個unicode字符串String str2=new String(byteArray1,"ISO-8859-1");System.out.println("轉成ISO-8859-1會亂碼"+str2);//將ISO-8859-1編碼的unicode字符串轉回為byte[]byte[] byteArray2=str2.getBytes("ISO-8859-1");//不會丟失數據//將字節流重新交回給用戶A//重新用UTF-8解碼String str3=new String(byteArray2,"UTF-8");System.out.println("數據沒有丟失"+str3); }輸出
unicode字符串用戶 6 轉成ISO-8859-1會亂碼?”¨??· 數據沒有丟失用戶用GBK中轉UTF-8數據
重復前面的流程將ISO-8859-1 用GBK替換。
只把中間一段改掉
//將byteArray1當做一個普通的字節流按照GBK解碼為一個unicode字符串String str2=new String(byteArray1,"GBK");System.out.println("轉成GBK會亂碼"+str2);//將GBK編碼的unicode字符串轉回為byte[]byte[] byteArray2=str2.getBytes("GBK");//數據會不會丟失呢運行結果
unicode字符串用戶 6 轉成GBK會亂碼鐢ㄦ埛 數據沒有丟失用戶好像沒有問題這就是一個誤區。
修改原文字符串重新測試
將兩個漢字 “用戶” 修改為三個漢字 “用戶名” 重新測試。
ISO-8859-1測試結果
unicode字符串用戶名 9 轉成GBK會亂碼?”¨??·? 數據沒有丟失用戶名GBK 測試結果
unicode字符串用戶名 9 轉成GBK會亂碼鐢ㄦ埛鍚 數據沒有丟失用戶?結論出來了
ISO-8859-1 可以作為中間編碼不會導致數據丟失
GBK 如果漢字數量為偶數不會丟失數據如果漢字數量為奇數必定會丟失數據。
why
為什么奇數個漢字GBK會出錯
直接對比兩種字符集和奇偶字數的情形
重新封裝一下前面的邏輯寫一段代碼來分析
public static void demo(String str) throws Exception {System.out.println("原文" + str);byte[] utfByte = str.getBytes("UTF-8");System.out.print("utf Byte");printHex(utfByte);String gbk = new String(utfByte, "GBK");//這里實際上把數據破壞了System.out.println("to GBK" + gbk);byte[] gbkByte=gbk.getBytes("GBK");String utf = new String(gbkByte, "UTF-8");System.out.print("gbk Byte");printHex(gbkByte);System.out.println("revert UTF8" + utf);System.out.println("==="); // 如果gbk變成iso-8859-1就沒問題 }public static void printHex(byte[] byteArray) {StringBuffer sb = new StringBuffer();for (byte b : byteArray) {sb.append(Integer.toHexString((b >> 4) & 0xF));sb.append(Integer.toHexString(b & 0xF));sb.append(" ");}System.out.println(sb.toString()); };public static void main(String[] args) throws Exception {String str1 = "姓名";String str2 = "用戶名";demo(str1,"UTF-8","ISO-8859-1");demo(str2,"UTF-8","ISO-8859-1");demo(str1,"UTF-8","GBK");demo(str2,"UTF-8","GBK"); }輸出結果
原文姓名 UTF-8 Bytee5 a7 93 e5 90 8d to ISO-8859-1:?§“? ISO-8859-1 Bytee5 a7 93 e5 90 8d revert UTF-8姓名 === 原文用戶名 UTF-8 Bytee7 94 a8 e6 88 b7 e5 90 8d to ISO-8859-1:?”¨??·? ISO-8859-1 Bytee7 94 a8 e6 88 b7 e5 90 8d revert UTF-8用戶名 === 原文姓名 UTF-8 Bytee5 a7 93 e5 90 8d to GBK:濮撳悕 GBK Bytee5 a7 93 e5 90 8d revert UTF-8姓名 === 原文用戶名 UTF-8 Bytee7 94 a8 e6 88 b7 e5 90 8d to GBK:鐢ㄦ埛鍚 GBK Bytee7 94 a8 e6 88 b7 e5 90 3f revert UTF-8用戶? ===為什么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。
public static void main(String[] args) throws Exception {String str="測試";printHex(str.getBytes("UNICODE"));printHex(str.getBytes("UTF-16LE"));printHex(str.getBytes("UTF-16BE")); }運行結果
fe ff 6d 4b 8b d5 4b 6d d5 8b 6d 4b 8b d5其中 “fe ff” 為大端消息頭同理小端消息頭為 “ff fe”。
小結
作為中間轉存方案ISO-8859-1 是安全的。
UTF-8 字節流用GBK字符集中轉是不安全的反過來也是同樣的道理。
byte[] utfByte = str.getBytes("UTF-8"); String gbk = new String(utfByte, "GBK"); 這是錯誤的用法雖然在ISO-8859-1時并沒報錯。首先byte[] utfByte = str.getBytes("UTF-8"); 執行完成之后utfByte 已經很明確這是utf-8格式的字節流然后gbk = new String(utfByte, "GBK") 對utf-8的字節流使用gbk解碼這是不合規矩的。就好比一個美國人說一段英語讓一個不懂英文又不會學舌的日本人聽然后傳遞消息給另一個美國人。為什么ISO-8859-1 沒問題呢因為它只認識一個一個的字節就相當于是一個錄音機。我管你說的什么鬼話連篇過去直接播放就可以了。getBytes() 是會丟失數據的操作而且不一定會拋異常。
unicode是安全的因為他是java使用的標準類型跨平臺無差異。
總結
以上是生活随笔為你收集整理的再谈java乱码:GBK和UTF-8互转尾部乱码问题分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Pandas.plot 做图 demo(
- 下一篇: 构造方法和方法的重载。