2014-07-15

偵測檔案使用的編碼是Utf-8或Big5

使用程式處理文字檔,最麻煩的就是要選擇開啟檔案時所採用的編碼方式。在台灣常見的就是utf-8或是big5兩種編碼,只要選錯了,那麼檔案裡面的中文字99.999%以上會變成亂碼或是莫名其妙的中文字。這也是非英語系國家的痛啊!



當編碼配上XML,就會更慘...公司某專案就是每天固定要處理廠商所上傳的訂單XML檔,解析存到資料庫並做後續處理。但常常就有時候無法解析,常發生的就是編碼的問題,由於XML裡面也可帶Declaration指定編碼(如<?xml version="1.0" encoding="utf-8" ?>),但某些廠商不曉得是怎樣,總是有可能會帶錯誤的編碼或是不指定,就變成可能會有底下的情況:


  1. 檔案utf-8編碼,不帶Declaration
  2. 檔案utf-8編碼,帶Declaration,encoding指定為utf-8
  3. 檔案utf-8編碼,帶Declaration,encoding指定為big5
  4. 檔案big5編碼,不帶Declaration
  5. 檔案big5編碼,帶Declaration,encoding指定為big5
  6. 檔案big5編碼,帶Declaration,encoding指定為utf-8
其中3和6的情況的廠商還真有,不曉得頭腦是發生甚麼事了...

而裡面還會有可能帶入XML不允許的特殊字元,這樣會造成程式無法解析的錯誤,所以我們必須先將裡面的特殊字元給過濾掉。但檔案總是要先開啟才能處理啊,這又回到一開始的問題,沒辦法知道檔案是用什麼編碼方式來編的。

如果採utf-8編碼,裡面有BOM,那就很好解決,.net程式很聰明,不管用utf-8或是big5的Encoding都可正常開啟,中文不會有亂碼。最怕的就是沒有BOM的utf-8編碼檔案,會跟big5混淆,選擇錯誤的編碼方式開啟就會造成檔案內的中文全變亂碼...

網路上搜尋過很多方式,多半都是教你判斷有EF BB BF就是utf-8,反之則不是,這根本沒講到重點,直到搜尋到黑暗執行緒的Jeffrey大所寫的一篇文章,他採用另一種思維方式:
(連結:CODE-偵測檔案是否為BIG5編碼)

直接以二進位讀取檔案byte到陣列(byteAry1),然後以big5轉成文字,再轉成big5 byte陣列(byteAry2),判斷兩個陣列長度是否相同,如果相同,則表示可用big5開啟沒問題,反之則表示該採utf-8開。

上面那段可能有點繞口令搞不太清楚,所以我用我的理解來解釋一下:

utf-8對非標準ascii字元都採3個bytes編碼,而big5則是2個bytes。
假設「你好」,用utf-8編碼是 A1 A2 A3 B1 B2 B3,A1 A2 A3表示「你」,B1 B2 B3表示「好」;同樣的bytes變成big5編碼規則的話,A1 A2是第一個中文字(假設對應到「」),A3 B1是第二個中文字(假設對應到「」),B2 B3是第三個中文字。但這樣編碼在big5裡並不會一定都有文字可對應,於是對應不到的中文字就會變成Ascii標準字元半形問號(?),而半形問號轉成byte編碼長度就只有1個byte,假設B2 B3在big5裡沒有中文字,所以對應不到的位元組轉成文字會變成「?」,然後再轉byte就變成3F(?的編碼)。這樣編碼會變成A1 A2 A3 B1 3F,長度只有5,與原始陣列長度6不相同,我們也就知道這個檔案不是big5編碼的了。


原始碼由暗黑執行緒Jeffrey大大提供:
//偵測byte[]是否為BIG5編碼
public static bool IsBig5Encoding(byte[] bytes) {
    Encoding big5 = Encoding.GetEncoding(950);
    //將byte[]轉為string再轉回byte[]看位元數是否有變
    return bytes.Length ==
        big5.GetByteCount(big5.GetString(bytes));
}
//偵測檔案否為BIG5編碼
public static bool IsBig5Encoding(string file) {
    if (!File.Exists(file)) return false;
    return IsBig5Encoding(File.ReadAllBytes(file));
}

如此可輕鬆地得知檔案的編碼了。

但,此方法不是100%準確,上面提到big5對應不到的編碼會變成?,而要有?,位元組陣列長度才會-1,但萬一原始utf-8三個一組的編碼,變成兩個一組的big5編碼,通通都對應的到字的話呢?陣列長度還是會一樣,函數會回傳True,這時如果以Big5編碼打開檔案,就會看到以亂碼顯示的文字了。回報之後Jeffrey大大的回應如下:
你所說的誤判狀況是存在的,因UTF-8與BIG5的編碼區間重疊,故此法要達100%精準判斷是不可能的。

所幸,當文字數愈多,其UTF-8編碼要完全相容BIG5的機率就愈低,在我的實務經驗裡,這個極簡便但非絕對精準的做法處理實際資料尚未出錯過(原因是系統所輸入文字檔均有一定長度),加上誤判造成的損失有限,仍可視為有效的解決方案。
所以這個方法還是有效的解決方案,比只判斷EF BB BF的方式好太多了!!!