日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

java中文GBK和UTF-8編碼轉(zhuǎn)換亂碼的分析

 Levy_X 2017-10-11

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

作者:54powerman

一直以為,java中任意unicode字符串,可以使用任意字符集轉(zhuǎn)為byte[]再轉(zhuǎn)回來,只要不拋出異常就不會丟失數(shù)據(jù),事實證明這是錯的。

經(jīng)過這個實例,也明白了為什么 getBytes()需要捕獲異常,雖然有時候它也沒有捕獲到異常。

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

用ISO-8859-1中轉(zhuǎn)UTF-8數(shù)據(jù)

設(shè)想一個場景:

用戶A,有一個UTF-8編碼的字節(jié)流,通過一個接口傳遞給用戶B;

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

在一定的處理流程處理后,把這個字節(jié)流交給用戶C或者交還給用戶A,他們都知道這是UTF-8,他們解碼得到的數(shù)據(jù),不會丟失。

下面代碼驗證:

01public static void main(String[] args) throws Exception {
02  //這是一個unicode字符串,與字符集無關(guān)
03  String str1 = '用戶';
04
05  System.out.println('unicode字符串:' str1);
06
07  //將str轉(zhuǎn)為UTF-8字節(jié)流
08  byte[] byteArray1=str1.getBytes('UTF-8');//這個很安全,UTF-8不會造成數(shù)據(jù)丟失
09
10  System.out.println(byteArray1.length);//打印6,沒毛病
11
12  //下面交給另外一個人,他不知道這是UTF-8字節(jié)流,因此他當(dāng)做ISO-8859-1處理
13
14  //將byteArray1當(dāng)做一個普通的字節(jié)流,按照ISO-8859-1解碼為一個unicode字符串
15  String str2=new String(byteArray1,'ISO-8859-1');
16
17  System.out.println('轉(zhuǎn)成ISO-8859-1會亂碼:' str2);
18
19  //將ISO-8859-1編碼的unicode字符串轉(zhuǎn)回為byte[]
20  byte[] byteArray2=str2.getBytes('ISO-8859-1');//不會丟失數(shù)據(jù)
21
22  //將字節(jié)流重新交回給用戶A
23
24  //重新用UTF-8解碼
25  String str3=new String(byteArray2,'UTF-8');
26
27  System.out.println('數(shù)據(jù)沒有丟失:' str3);
28}

輸出:

1unicode字符串:用戶
26
3轉(zhuǎn)成ISO-8859-1會亂碼:?”¨??·
4數(shù)據(jù)沒有丟失:用戶

用GBK中轉(zhuǎn)UTF-8數(shù)據(jù)

重復(fù)前面的流程,將ISO-8859-1 用GBK替換。

只把中間一段改掉:

1//將byteArray1當(dāng)做一個普通的字節(jié)流,按照GBK解碼為一個unicode字符串
2    String str2=new String(byteArray1,'GBK');
3
4    System.out.println('轉(zhuǎn)成GBK會亂碼:' str2);
5
6    //將GBK編碼的unicode字符串轉(zhuǎn)回為byte[]
7    byte[] byteArray2=str2.getBytes('GBK');//數(shù)據(jù)會不會丟失呢?

運(yùn)行結(jié)果:

1unicode字符串:用戶
26
3轉(zhuǎn)成GBK會亂碼:鐢ㄦ埛
4數(shù)據(jù)沒有丟失:用戶

好像沒有問題,這就是一個誤區(qū)。

修改原文字符串重新測試

將兩個漢字 “用戶” 修改為三個漢字 “用戶名” 重新測試。

ISO-8859-1測試結(jié)果:

1unicode字符串:用戶名
29
3轉(zhuǎn)成GBK會亂碼:?”¨??·???
4數(shù)據(jù)沒有丟失:用戶名

GBK 測試結(jié)果:

1unicode字符串:用戶名
29
3轉(zhuǎn)成GBK會亂碼:鐢ㄦ埛鍚?
4數(shù)據(jù)沒有丟失:用戶??

結(jié)論出來了

ISO-8859-1 可以作為中間編碼,不會導(dǎo)致數(shù)據(jù)丟失;

GBK 如果漢字?jǐn)?shù)量為偶數(shù),不會丟失數(shù)據(jù),如果漢字?jǐn)?shù)量為奇數(shù),必定會丟失數(shù)據(jù)。

why?

為什么奇數(shù)個漢字GBK會出錯

直接對比兩種字符集和奇偶字?jǐn)?shù)的情形

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

01public static void demo(String str) throws Exception {
02  System.out.println('原文:'  str);
03
04  byte[] utfByte = str.getBytes('UTF-8');
05  System.out.print('utf Byte:');
06  printHex(utfByte);
07  String gbk = new String(utfByte, 'GBK');//這里實際上把數(shù)據(jù)破壞了
08  System.out.println('to GBK:'  gbk);
09
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
19public 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
29public 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}

輸出結(jié)果:

01原文:姓名
02UTF-8 Byte:e5 a7 93 e5 90 8d
03to ISO-8859-1:?§“???
04ISO-8859-1 Byte:e5 a7 93 e5 90 8d
05revert UTF-8:姓名
06===
07原文:用戶名
08UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
09to ISO-8859-1:?”¨??·???
10ISO-8859-1 Byte:e7 94 a8 e6 88 b7 e5 90 8d
11revert UTF-8:用戶名
12===
13原文:姓名
14UTF-8 Byte:e5 a7 93 e5 90 8d
15to GBK:濮撳悕
16GBK Byte:e5 a7 93 e5 90 8d
17revert UTF-8:姓名
18===
19原文:用戶名
20UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
21to GBK:鐢ㄦ埛鍚?
22GBK Byte:e7 94 a8 e6 88 b7 e5 90 3f
23revert UTF-8:用戶??
24===

為什么GBK會出錯

前三段都沒問題,最后一段,奇數(shù)個漢字的utf-8字節(jié)流轉(zhuǎn)成GBK字符串,再轉(zhuǎn)回來,前面一切正常,最后一個字節(jié),變成了 “0x3f”,即”?”

我們使用”用戶名” 三個字來分析,它的UTF-8 的字節(jié)流為:

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

我們按照三個字節(jié)一組分組,他被用戶A當(dāng)做一個整體交給用戶B。

用戶B由于不知道是什么字符集,他當(dāng)做GBK處理,因為GBK是雙字節(jié)編碼,如下按照兩兩一組進(jìn)行分組:

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

不夠了,怎么辦?它把 0x8d當(dāng)做一個未知字符,用一個半角Ascii字符的 “?” 代替,變成了:

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

數(shù)據(jù)被破壞了。

為什么 ISO-8859-1 沒問題

因為 ISO-8859-1 是單字節(jié)編碼,因此它的分組方案是:

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

因此中間不做任何操作,交回個用戶A的時候,數(shù)據(jù)沒有變化。

關(guān)于Unicode編碼

因為UTF-16 區(qū)分大小端,嚴(yán)格講:unicode==UTF16BE。

view sourceprint?

1public 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}

運(yùn)行結(jié)果:

1fe ff 6d 4b 8b d5
24b 6d d5 8b
36d 4b 8b d5

其中 “fe ff” 為大端消息頭,同理,小端消息頭為 “ff fe”。

小結(jié)

作為中間轉(zhuǎn)存方案,ISO-8859-1 是安全的。

UTF-8 字節(jié)流,用GBK字符集中轉(zhuǎn)是不安全的;反過來也是同樣的道理。

01byte[] utfByte = str.getBytes('UTF-8');
02String gbk = new String(utfByte, 'GBK');
03這是錯誤的用法,雖然在ISO-8859-1時并沒報錯。
04
05首先,byte[] utfByte = str.getBytes('UTF-8');
06執(zhí)行完成之后,utfByte 已經(jīng)很明確,這是utf-8格式的字節(jié)流;
07
08然后,gbk = new String(utfByte, 'GBK'),
09對utf-8的字節(jié)流使用gbk解碼,這是不合規(guī)矩的。
10
11就好比一個美國人說一段英語,讓一個不懂英文又不會學(xué)舌的日本人聽,然后傳遞消息給另一個美國人。
12
13為什么ISO-8859-1 沒問題呢?
14
15因為它只認(rèn)識一個一個的字節(jié),就相當(dāng)于是一個錄音機(jī)。我管你說的什么鬼話連篇,過去直接播放就可以了。

getBytes() 是會丟失數(shù)據(jù)的操作,而且不一定會拋異常。

unicode是安全的,因為他是java使用的標(biāo)準(zhǔn)類型,跨平臺無差異。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多