我在通過Java調用aapt來獲取apk的應用名稱時,發現如果應用名稱爲中文,則會出現亂碼,例如:
'VlogStar鍗$偣瑙嗛蹇壀杈戣蔣浠?'
怎麼辦呢?當然是轉碼了:
applicationLabelMap.put(language, new String(label.getBytes(MyDocumentManager.ENCODING_CN), MyDocumentManager.ENCODING));
問題似乎解決了,以GBK解碼,重新以UTF8編碼,中文正常顯示了。但中文並不總是正常顯示,這與字符數有關,當字符數爲奇數時,最後一箇中文字符顯示不正常:
'VlogStar鍗$偣瑙嗛蹇壀杈戣蔣浠?' > 'VlogStar卡點視頻快剪輯軟??'
爲什麼會這樣呢?因爲字符串在轉碼過程中發生了錯誤,雖然沒有任何異常產生,但數據已經發生了損壞。
針對我這一問題的解決辦法,就是在讀取aapt的輸出流的時候,指定編碼:
@Override
public final void run()
{
if(null != this.is)
{
this.outputList.clear();
InputStreamReader ir = null;
BufferedReader br = null;
try
{
ir = new InputStreamReader(this.is, this.encoding);
br = new BufferedReader(ir);
String text = null;
while(null != (text = br.readLine()))
{
this.append(text);
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
this.close(br);
this.close(ir);
this.close(this.is);
}
}
}
至於GBK和UTF-8互轉尾部亂碼問題,可以參考:
原文地址:https://blog.csdn.net/54powerman/article/details/77575656
一直以爲,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 Byte:e5 a7 93 e5 90 8d
to ISO-8859-1:姓å
ISO-8859-1 Byte:e5 a7 93 e5 90 8d
revert UTF-8:姓名
===
原文:用戶名
UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
to ISO-8859-1:用户å
ISO-8859-1 Byte:e7 94 a8 e6 88 b7 e5 90 8d
revert UTF-8:用戶名
===
原文:姓名
UTF-8 Byte:e5 a7 93 e5 90 8d
to GBK:濮撳悕
GBK Byte:e5 a7 93 e5 90 8d
revert UTF-8:姓名
===
原文:用戶名
UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
to GBK:鐢ㄦ埛鍚�
GBK Byte:e7 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使用的標準類型,跨平臺無差異。