再谈java乱码:GBK和UTF-8互转尾部乱码问题分析
一直以為java中任意unicode字符串可以使用任意字符集轉(zhuǎn)為byte[]再轉(zhuǎn)回來(lái)只要不拋出異常就不會(huì)丟失數(shù)據(jù)事實(shí)證明這是錯(cuò)的。
經(jīng)過(guò)這個(gè)實(shí)例也明白了為什么 getBytes()需要捕獲異常雖然有時(shí)候它也沒(méi)有捕獲到異常。
言歸正傳先看一個(gè)實(shí)例。
用ISO-8859-1中轉(zhuǎn)UTF-8數(shù)據(jù)
設(shè)想一個(gè)場(chǎng)景
用戶(hù)A有一個(gè)UTF-8編碼的字節(jié)流通過(guò)一個(gè)接口傳遞給用戶(hù)B
用戶(hù)B并不知道是什么字符集他用ISO-8859-1來(lái)接收保存
在一定的處理流程處理后把這個(gè)字節(jié)流交給用戶(hù)C或者交還給用戶(hù)A他們都知道這是UTF-8他們解碼得到的數(shù)據(jù)不會(huì)丟失。
下面代碼驗(yàn)證
public static void main(String[] args) throws Exception {//這是一個(gè)unicode字符串與字符集無(wú)關(guān)String str1 = "用戶(hù)";System.out.println("unicode字符串"+str1);//將str轉(zhuǎn)為UTF-8字節(jié)流byte[] byteArray1=str1.getBytes("UTF-8");//這個(gè)很安全UTF-8不會(huì)造成數(shù)據(jù)丟失System.out.println(byteArray1.length);//打印6沒(méi)毛病//下面交給另外一個(gè)人他不知道這是UTF-8字節(jié)流因此他當(dāng)做ISO-8859-1處理//將byteArray1當(dāng)做一個(gè)普通的字節(jié)流按照ISO-8859-1解碼為一個(gè)unicode字符串String str2=new String(byteArray1,"ISO-8859-1");System.out.println("轉(zhuǎn)成ISO-8859-1會(huì)亂碼"+str2);//將ISO-8859-1編碼的unicode字符串轉(zhuǎn)回為byte[]byte[] byteArray2=str2.getBytes("ISO-8859-1");//不會(huì)丟失數(shù)據(jù)//將字節(jié)流重新交回給用戶(hù)A//重新用UTF-8解碼String str3=new String(byteArray2,"UTF-8");System.out.println("數(shù)據(jù)沒(méi)有丟失"+str3); }輸出
unicode字符串用戶(hù) 6 轉(zhuǎn)成ISO-8859-1會(huì)亂碼?”¨??· 數(shù)據(jù)沒(méi)有丟失用戶(hù)用GBK中轉(zhuǎn)UTF-8數(shù)據(jù)
重復(fù)前面的流程將ISO-8859-1 用GBK替換。
只把中間一段改掉
//將byteArray1當(dāng)做一個(gè)普通的字節(jié)流按照GBK解碼為一個(gè)unicode字符串String str2=new String(byteArray1,"GBK");System.out.println("轉(zhuǎn)成GBK會(huì)亂碼"+str2);//將GBK編碼的unicode字符串轉(zhuǎn)回為byte[]byte[] byteArray2=str2.getBytes("GBK");//數(shù)據(jù)會(huì)不會(huì)丟失呢運(yùn)行結(jié)果
unicode字符串用戶(hù) 6 轉(zhuǎn)成GBK會(huì)亂碼鐢ㄦ埛 數(shù)據(jù)沒(méi)有丟失用戶(hù)好像沒(méi)有問(wèn)題這就是一個(gè)誤區(qū)。
修改原文字符串重新測(cè)試
將兩個(gè)漢字 “用戶(hù)” 修改為三個(gè)漢字 “用戶(hù)名” 重新測(cè)試。
ISO-8859-1測(cè)試結(jié)果
unicode字符串用戶(hù)名 9 轉(zhuǎn)成GBK會(huì)亂碼?”¨??·? 數(shù)據(jù)沒(méi)有丟失用戶(hù)名GBK 測(cè)試結(jié)果
unicode字符串用戶(hù)名 9 轉(zhuǎn)成GBK會(huì)亂碼鐢ㄦ埛鍚 數(shù)據(jù)沒(méi)有丟失用戶(hù)?結(jié)論出來(lái)了
ISO-8859-1 可以作為中間編碼不會(huì)導(dǎo)致數(shù)據(jù)丟失
GBK 如果漢字?jǐn)?shù)量為偶數(shù)不會(huì)丟失數(shù)據(jù)如果漢字?jǐn)?shù)量為奇數(shù)必定會(huì)丟失數(shù)據(jù)。
why
為什么奇數(shù)個(gè)漢字GBK會(huì)出錯(cuò)
直接對(duì)比兩種字符集和奇偶字?jǐn)?shù)的情形
重新封裝一下前面的邏輯寫(xiě)一段代碼來(lái)分析
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");//這里實(shí)際上把數(shù)據(jù)破壞了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就沒(méi)問(wèn)題 }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 = "用戶(hù)名";demo(str1,"UTF-8","ISO-8859-1");demo(str2,"UTF-8","ISO-8859-1");demo(str1,"UTF-8","GBK");demo(str2,"UTF-8","GBK"); }輸出結(jié)果
原文姓名 UTF-8 Bytee5 a7 93 e5 90 8d to ISO-8859-1:?§“? ISO-8859-1 Bytee5 a7 93 e5 90 8d revert UTF-8姓名 === 原文用戶(hù)名 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用戶(hù)名 === 原文姓名 UTF-8 Bytee5 a7 93 e5 90 8d to GBK:濮撳悕 GBK Bytee5 a7 93 e5 90 8d revert UTF-8姓名 === 原文用戶(hù)名 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用戶(hù)? ===為什么GBK會(huì)出錯(cuò)
前三段都沒(méi)問(wèn)題最后一段奇數(shù)個(gè)漢字的utf-8字節(jié)流轉(zhuǎn)成GBK字符串再轉(zhuǎn)回來(lái)前面一切正常最后一個(gè)字節(jié)變成了 “0x3f”即”?”
我們使用”用戶(hù)名” 三個(gè)字來(lái)分析它的UTF-8 的字節(jié)流為
[e7 94 a8] [e6 88 b7] [e5 90 8d]
我們按照三個(gè)字節(jié)一組分組他被用戶(hù)A當(dāng)做一個(gè)整體交給用戶(hù)B。
用戶(hù)B由于不知道是什么字符集他當(dāng)做GBK處理因?yàn)镚BK是雙字節(jié)編碼如下按照兩兩一組進(jìn)行分組
[e7 94] [a8 e6] [88 b7] [e5 90] [8d ]
不夠了怎么辦它把 0x8d當(dāng)做一個(gè)未知字符用一個(gè)半角Ascii字符的 “” 代替變成了
[e7 94] [a8 e6] [88 b7] [e5 90] 3f
數(shù)據(jù)被破壞了。
為什么 ISO-8859-1 沒(méi)問(wèn)題
因?yàn)?ISO-8859-1 是單字節(jié)編碼因此它的分組方案是
[e7] [94] [a8] [e6] [88] [b7] [e5] [90] [8d]
因此中間不做任何操作交回個(gè)用戶(hù)A的時(shí)候數(shù)據(jù)沒(méi)有變化。
關(guān)于Unicode編碼
因?yàn)閁TF-16 區(qū)分大小端嚴(yán)格講unicode==UTF16BE。
public static void main(String[] args) throws Exception {String str="測(cè)試";printHex(str.getBytes("UNICODE"));printHex(str.getBytes("UTF-16LE"));printHex(str.getBytes("UTF-16BE")); }運(yùn)行結(jié)果
fe ff 6d 4b 8b d5 4b 6d d5 8b 6d 4b 8b d5其中 “fe ff” 為大端消息頭同理小端消息頭為 “ff fe”。
小結(jié)
作為中間轉(zhuǎn)存方案ISO-8859-1 是安全的。
UTF-8 字節(jié)流用GBK字符集中轉(zhuǎn)是不安全的反過(guò)來(lái)也是同樣的道理。
byte[] utfByte = str.getBytes("UTF-8"); String gbk = new String(utfByte, "GBK"); 這是錯(cuò)誤的用法雖然在ISO-8859-1時(shí)并沒(méi)報(bào)錯(cuò)。首先byte[] utfByte = str.getBytes("UTF-8"); 執(zhí)行完成之后utfByte 已經(jīng)很明確這是utf-8格式的字節(jié)流然后gbk = new String(utfByte, "GBK") 對(duì)utf-8的字節(jié)流使用gbk解碼這是不合規(guī)矩的。就好比一個(gè)美國(guó)人說(shuō)一段英語(yǔ)讓一個(gè)不懂英文又不會(huì)學(xué)舌的日本人聽(tīng)然后傳遞消息給另一個(gè)美國(guó)人。為什么ISO-8859-1 沒(méi)問(wèn)題呢因?yàn)樗徽J(rèn)識(shí)一個(gè)一個(gè)的字節(jié)就相當(dāng)于是一個(gè)錄音機(jī)。我管你說(shuō)的什么鬼話(huà)連篇過(guò)去直接播放就可以了。getBytes() 是會(huì)丟失數(shù)據(jù)的操作而且不一定會(huì)拋異常。
unicode是安全的因?yàn)樗莏ava使用的標(biāo)準(zhǔn)類(lèi)型跨平臺(tái)無(wú)差異。
總結(jié)
以上是生活随笔為你收集整理的再谈java乱码:GBK和UTF-8互转尾部乱码问题分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Pandas.plot 做图 demo(
- 下一篇: 构造方法和方法的重载。