計算機程序的思惟邏輯 (7) - 如何從亂碼中恢復 (下)?

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

亂碼

上節說到亂碼出現的主要緣由,即在進行編碼轉換的時候,若是將原來的編碼識別錯了,並進行了轉換,就會發生亂碼,並且這時候不管怎麼切換查看編碼的方式,都是不行的。java

咱們來看一個這種錯誤轉換後的亂碼,仍是用上節的例子,二進制是(16進製表示):C3 80 C3 8F C3 82 C3 AD,不管按哪一種編碼解析看上去都是亂碼:編程

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

雖然有這麼多形式,但咱們看到的亂碼形式極可能是"ÀÏÂí",由於在例子中UTF-8是編碼轉換的目標編碼格式,既然轉換爲了UTF-8,通常也是要按UTF-8查看。windows

亂碼恢復

"亂"主要是由於發生了一次錯誤的編碼轉換,恢復是要恢復兩個關鍵信息,一個是原來的二進制編碼方式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。spa

這個例子咱們碰巧第一次就猜對了。實際中,咱們可能要作屢次嘗試,過程是相似的,先進行編碼轉換(使用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編程及計算機技術的本質。原創文章,保留全部版權。

相關文章
相關標籤/搜索