原文:http://blog.csdn.net/54powerman/article/details/77575656 作者:54powerman
一直以為,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)景: 用戶A,有一個(gè)UTF-8編碼的字節(jié)流,通過(guò)一個(gè)接口傳遞給用戶B; 用戶B并不知道是什么字符集,他用ISO-8859-1來(lái)接收,保存; 在一定的處理流程處理后,把這個(gè)字節(jié)流交給用戶C或者交還給用戶A,他們都知道這是UTF-8,他們解碼得到的數(shù)據(jù),不會(huì)丟失。 下面代碼驗(yàn)證: 01 | public static void main(String[] args) throws Exception { |
02 | //這是一個(gè)unicode字符串,與字符集無(wú)關(guān) |
05 | System.out.println('unicode字符串:' str1); |
07 | //將str轉(zhuǎn)為UTF-8字節(jié)流 |
08 | byte[] byteArray1=str1.getBytes('UTF-8');//這個(gè)很安全,UTF-8不會(huì)造成數(shù)據(jù)丟失 |
10 | System.out.println(byteArray1.length);//打印6,沒(méi)毛病 |
12 | //下面交給另外一個(gè)人,他不知道這是UTF-8字節(jié)流,因此他當(dāng)做ISO-8859-1處理 |
14 | //將byteArray1當(dāng)做一個(gè)普通的字節(jié)流,按照ISO-8859-1解碼為一個(gè)unicode字符串 |
15 | String str2=new String(byteArray1,'ISO-8859-1'); |
17 | System.out.println('轉(zhuǎn)成ISO-8859-1會(huì)亂碼:' str2); |
19 | //將ISO-8859-1編碼的unicode字符串轉(zhuǎn)回為byte[] |
20 | byte[] byteArray2=str2.getBytes('ISO-8859-1');//不會(huì)丟失數(shù)據(jù) |
25 | String str3=new String(byteArray2,'UTF-8'); |
27 | System.out.println('數(shù)據(jù)沒(méi)有丟失:' str3); |
輸出: 3 | 轉(zhuǎn)成ISO-8859-1會(huì)亂碼:?”¨??· |
用GBK中轉(zhuǎn)UTF-8數(shù)據(jù) 重復(fù)前面的流程,將ISO-8859-1 用GBK替換。 只把中間一段改掉: 1 | //將byteArray1當(dāng)做一個(gè)普通的字節(jié)流,按照GBK解碼為一個(gè)unicode字符串 |
2 | String str2=new String(byteArray1,'GBK'); |
4 | System.out.println('轉(zhuǎn)成GBK會(huì)亂碼:' str2); |
6 | //將GBK編碼的unicode字符串轉(zhuǎn)回為byte[] |
7 | byte[] byteArray2=str2.getBytes('GBK');//數(shù)據(jù)會(huì)不會(huì)丟失呢? |
運(yùn)行結(jié)果: 3 | 轉(zhuǎn)成GBK會(huì)亂碼:鐢ㄦ埛 |
好像沒(méi)有問(wèn)題,這就是一個(gè)誤區(qū)。 修改原文字符串重新測(cè)試 將兩個(gè)漢字 “用戶” 修改為三個(gè)漢字 “用戶名” 重新測(cè)試。 ISO-8859-1測(cè)試結(jié)果: 3 | 轉(zhuǎn)成GBK會(huì)亂碼:?”¨??·??? |
4 | 數(shù)據(jù)沒(méi)有丟失:用戶名 |
GBK 測(cè)試結(jié)果: 3 | 轉(zhuǎn)成GBK會(huì)亂碼:鐢ㄦ埛鍚? |
4 | 數(shù)據(jù)沒(méi)有丟失:用戶?? |
結(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)分析: 01 | public static void demo(String str) throws Exception { |
02 | System.out.println('原文:' str); |
04 | byte[] utfByte = str.getBytes('UTF-8'); |
05 | System.out.print('utf Byte:'); |
07 | String gbk = new String(utfByte, 'GBK');//這里實(shí)際上把數(shù)據(jù)破壞了 |
08 | System.out.println('to GBK:' gbk); |
10 | byte[] gbkByte=gbk.getBytes('GBK'); |
11 | String utf = new String(gbkByte, 'UTF-8'); |
12 | System.out.print('gbk Byte:'); |
14 | System.out.println('revert UTF8:' utf); |
15 | System.out.println('==='); |
16 | // 如果gbk變成iso-8859-1就沒(méi)問(wèn)題 |
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)); |
26 | System.out.println(sb.toString()); |
29 | public static void main(String[] args) throws Exception { |
32 | demo(str1,'UTF-8','ISO-8859-1'); |
33 | demo(str2,'UTF-8','ISO-8859-1'); |
35 | demo(str1,'UTF-8','GBK'); |
36 | demo(str2,'UTF-8','GBK'); |
輸出結(jié)果: 02 | UTF-8 Byte:e5 a7 93 e5 90 8d |
04 | ISO-8859-1 Byte:e5 a7 93 e5 90 8d |
08 | UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d |
09 | to ISO-8859-1:?”¨??·??? |
10 | ISO-8859-1 Byte:e7 94 a8 e6 88 b7 e5 90 8d |
14 | UTF-8 Byte:e5 a7 93 e5 90 8d |
16 | GBK Byte:e5 a7 93 e5 90 8d |
20 | UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d |
22 | GBK Byte:e7 94 a8 e6 88 b7 e5 90 3f |
為什么GBK會(huì)出錯(cuò) 前三段都沒(méi)問(wèn)題,最后一段,奇數(shù)個(gè)漢字的utf-8字節(jié)流轉(zhuǎn)成GBK字符串,再轉(zhuǎn)回來(lái),前面一切正常,最后一個(gè)字節(jié),變成了 “0x3f”,即”?” 我們使用”用戶名” 三個(gè)字來(lái)分析,它的UTF-8 的字節(jié)流為: [e7 94 a8] [e6 88 b7] [e5 90 8d] 我們按照三個(gè)字節(jié)一組分組,他被用戶A當(dāng)做一個(gè)整體交給用戶B。 用戶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è)用戶A的時(shí)候,數(shù)據(jù)沒(méi)有變化。 關(guān)于Unicode編碼 因?yàn)閁TF-16 區(qū)分大小端,嚴(yán)格講:unicode==UTF16BE。 view sourceprint? 1 | public static void main(String[] args) throws Exception { |
3 | printHex(str.getBytes('UNICODE')); |
4 | printHex(str.getBytes('UTF-16LE')); |
5 | printHex(str.getBytes('UTF-16BE')); |
運(yùn)行結(jié)果: 其中 “fe ff” 為大端消息頭,同理,小端消息頭為 “ff fe”。 小結(jié) 作為中間轉(zhuǎn)存方案,ISO-8859-1 是安全的。 UTF-8 字節(jié)流,用GBK字符集中轉(zhuǎn)是不安全的;反過(guò)來(lái)也是同樣的道理。 01 | byte[] utfByte = str.getBytes('UTF-8'); |
02 | String gbk = new String(utfByte, 'GBK'); |
03 | 這是錯(cuò)誤的用法,雖然在ISO-8859-1時(shí)并沒(méi)報(bào)錯(cuò)。 |
05 | 首先,byte[] utfByte = str.getBytes('UTF-8'); |
06 | 執(zhí)行完成之后,utfByte 已經(jīng)很明確,這是utf-8格式的字節(jié)流; |
08 | 然后,gbk = new String(utfByte, 'GBK'), |
09 | 對(duì)utf-8的字節(jié)流使用gbk解碼,這是不合規(guī)矩的。 |
11 | 就好比一個(gè)美國(guó)人說(shuō)一段英語(yǔ),讓一個(gè)不懂英文又不會(huì)學(xué)舌的日本人聽(tīng),然后傳遞消息給另一個(gè)美國(guó)人。 |
13 | 為什么ISO-8859-1 沒(méi)問(wèn)題呢? |
15 | 因?yàn)樗徽J(rèn)識(shí)一個(gè)一個(gè)的字節(jié),就相當(dāng)于是一個(gè)錄音機(jī)。我管你說(shuō)的什么鬼話連篇,過(guò)去直接播放就可以了。 |
getBytes() 是會(huì)丟失數(shù)據(jù)的操作,而且不一定會(huì)拋異常。 unicode是安全的,因?yàn)樗莏ava使用的標(biāo)準(zhǔn)類型,跨平臺(tái)無(wú)差異。
|