天天看點

再談java亂碼:GBK和UTF-8互轉尾部亂碼問題分析

一直以為java中任意unicode字元串可以使用任意字元集轉為byte[]再轉回來隻要不抛出異常就不會丢失資料事實證明這是錯的。

經過這個執行個體也明白了為什麼 getBytes()需要捕獲異常雖然有時候它也沒有捕獲到異常。

言歸正傳先看一個執行個體。

設想一個場景

使用者A有一個UTF-8編碼的位元組流通過一個接口傳遞給使用者B

使用者B并不知道是什麼字元集他用ISO-8859-1來接收儲存

在一定的處理流程處理後把這個位元組流交給使用者C或者交還給使用者A他們都知道這是UTF-8他們解碼得到的資料不會丢失。

下面代碼驗證

輸出

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

隻把中間一段改掉

運作結果

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

将兩個漢字 “使用者” 修改為三個漢字 “使用者名” 重新測試。

ISO-8859-1測試結果

GBK 測試結果

ISO-8859-1 可以作為中間編碼不會導緻資料丢失

GBK 如果漢字數量為偶數不會丢失資料如果漢字數量為奇數必定會丢失資料。

why

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

輸出結果

前三段都沒問題最後一段奇數個漢字的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 是單位元組編碼是以它的分組方案是

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

是以中間不做任何操作交回個使用者A的時候資料沒有變化。

因為UTF-16 區分大小端嚴格講unicode==UTF16BE。

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

作為中間轉存方案ISO-8859-1 是安全的。

UTF-8 位元組流用GBK字元集中轉是不安全的反過來也是同樣的道理。

getBytes() 是會丢失資料的操作而且不一定會抛異常。

unicode是安全的因為他是java使用的标準類型跨平台無差異。