Java編程的邏輯 (7) - 如何從亂碼中恢復 (下)?


本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》,由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買,京東自營鏈接http://item.jd.com/12299018.html


亂碼

上節說到亂碼出現的主要原因,即在進行編碼轉換的時候,如果將原來的編碼識別錯了,並進行了轉換,就會發生亂碼,而且這時候無論怎么切換查看編碼的方式,都是不行的。

我們來看一個這種錯誤轉換后的亂碼,還是用上節的例子,二進制是(16進制表示):C3 80 C3 8F C3 82 C3 AD,無論按哪種編碼解析看上去都是亂碼:

UTF-8 ÀÏÂí
Windows-1252 ÀÏÂí
GB18030 脌脧脗鉚
Big5 ���穩

雖然有這么多形式,但我們看到的亂碼形式很可能是"ÀÏÂí",因為在例子中UTF-8是編碼轉換的目標編碼格式,既然轉換為了UTF-8,一般也是要按UTF-8查看。

亂碼恢復

"亂"主要是因為發生了一次錯誤的編碼轉換,恢復是要恢復兩個關鍵信息,一個是原來的二進制編碼方式A,另一個是錯誤解讀的編碼方式B。

恢復的基本思路是嘗試進行逆向操作,假定按一種編碼轉換方式B獲取亂碼的二進制格式,然后再假定一種編碼解讀方式A解讀這個二進制,查看其看上去的形式,這個要嘗試多種編碼,如果能找到看着正常的字符形式,那應該就可以恢復。

這個聽上去可能比較模糊,我們舉個例子來說明,假定亂碼形式是"ÀÏÂí",嘗試多種B和A來看字符形式。我們先使用編輯器,以UltraEdit為例,然后使用Java編程來看。

使用UltraEdit

UltraEdit支持編碼轉換和切換查看編碼方式,也支持文件的二進制顯示和編輯,所以我們以UltraEdit為例,其他一些編輯器可能也有類似功能。

新建一個UTF-8編碼的文件,拷貝"ÀÏÂí"到文件中。使用編碼轉換,轉換到windows-1252編碼,功能在 "文件"->"轉換到"->"西歐"->WIN-1252。
轉換完后,打開十六進制編輯,查看其二進制形式,如下圖所示:

 

可以看出,其形式還是ÀÏÂí,但二進制格式變成了 C0 CF C2 ED。這個過程,相當於假設B是windows-1252。這個時候,再按照多種編碼格式查看這個二進制,在UltraEdit中,關閉十六進制編輯,切換查看編碼方式為GB18030,功能在 "視圖"->"查看方式(文件編碼)"->"東亞語言"->GB18030,切換完后,同樣的二進制神奇的變為了正確的字符形式 "老馬",打開十六進制編輯器,可以看出,二進制還是C0 CF C2 ED,這個GB18030相當於假設A是GB18030。

這個例子我們碰巧第一次就猜對了。實際中,我們可能要做多次嘗試,過程是類似的,先進行編碼轉換(使用B編碼),然后使用不同編碼方式查看(使用A編碼),如果能找到看上去對的形式,就恢復了。下圖列出了主要的B編碼格式,對應的二進制,按A編碼解讀的各種形式。

可以看出,第一行是正確的,也就是說原來的編碼其實是A即GB18030,但被錯誤解讀成了B即Windows-1252了。

使用Java

關於使用Java我們還有很多知識沒有介紹,但一些讀者已經有很好的Java知識,所以本文一並列出相關代碼,初學者不明白的我們隨后會進一步講解。

Java中處理字符串的類有String,String中有我們需要的兩個重要方法:

  • public byte[] getBytes(String charsetName),這個方法可以獲取一個字符串的給定編碼格式的二進制形式
  • public String(byte bytes[], String charsetName),這個構造方法以給定的二進制數組bytes按照編碼格式charsetName解讀為一個字符串。

將A看做GB18030,B看做Windows-1252,進行恢復的Java代碼如下所示:

String str = "ÀÏÂí";
String newStr = new String(str.getBytes("windows-1252"),"GB18030");
System.out.println(newStr);

先按照B編碼(windows-1252)獲取字符串的二進制,然后按A編碼(GB18030)解讀這個二進制,得到一個新的字符串,然后輸出這個字符串的形式,輸出為"老馬"。

同樣,這個一次碰巧就對了,實際中,我們可以寫一個循環,測試不同的A/B編碼中的結果形式,代碼如下所示:

public static void recover(String str) 
        throws UnsupportedEncodingException{
    String[] charsets = new String[]{"windows-1252","GB18030","Big5","UTF-8"};
    for(int i=0;i<charsets.length;i++){
        for(int j=0;j<charsets.length;j++){
            if(i!=j){
                String s = new String(str.getBytes(charsets[i]),charsets[j]);
                System.out.println("---- 原來編碼(A)假設是: "+charsets[j]+", 被錯誤解讀為了(B): "+charsets[i]);
                System.out.println(s);
                System.out.println();    
            }
        }
    }
} 

以上代碼使用不同的編碼格式進行測試,如果輸出有正確的,那么就可以恢復。

恢復的討論

可以看出,這種嘗試需要進行很多次,上面例子嘗試了常見編碼GB18030/Windows 1252/Big5/UTF-8共十二種組合。這四種編碼是常見編碼,在大部分實際應用中應該夠了,但如果你的情況有其他編碼,可以增加一些嘗試。

不是所有的亂碼形式都是可以恢復的,如果形式中有很多不能識別的字符如�?,則很難恢復,另外,如果亂碼是由於進行了多次解析和轉換錯誤造成的,也很難恢復。

小結

上節和本節介紹了編碼的知識,亂碼的原因及恢復方法,這些都是與語言無關的。

接下來,是時候看看在Java中如何表示和處理字符了,我們知道Java中用char類型表示一個字符,但在第三節我們提到了一個問題,即"字符類型怎么也可以進行算術運算和比較?"。

我們需要對Java中的字符類型有一個更為清晰和深刻的理解。

----------------

未完待續,查看最新文章,敬請關注微信公眾號“老馬說編程”(掃描下方二維碼),深入淺出,老馬和你一起探索Java編程及計算機技術的本質。原創文章,保留所有版權。

-----------

更多相關原創文章

計算機程序的思維邏輯 (1) - 數據和變量

計算機程序的思維邏輯 (2) - 賦值

計算機程序的思維邏輯 (3) - 基本運算

計算機程序的思維邏輯 (4) - 整數的二進制表示與位運算

計算機程序的思維邏輯 (5) - 小數計算為什么會出錯?

計算機程序的思維邏輯 (6) - 如何從亂碼中恢復 (上)?

計算機程序的思維邏輯 (8) - char的真正含義


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM