CODE-偵測檔案是否為BIG5編碼
15 | 49,390 |
有個偵測文字檔是否為BIG5編碼的需求,決定寫個小函數來實現。
要偵測BIG5編碼,有一種寫法是將字串轉為byte[]後再比對0xA440-0xC67E等編碼區間(參考)加以識別;不過,我不太喜歡這類寫法,理由是既然BIG5編碼邏輯細節已存在.NET內建Encoding類別中(可透過Encoding.GetEncoding(950)或Encoding.GetEncoding("big5")取得),編碼區間定義另外出現在我們的程式碼多少違背了"觀注點分離(SoC), Separation of Concerns"哲學。BIG5編碼所有的細節應該由專屬Encoding類別掌管,其他程式碼不需了解也不應涉及,若編碼邏輯需變動時,才不致牽連其他程式也得跟著調整,如此才符合SoC的精神。(雖然BIG5編碼規則未來改變的機率跟林志玲成為我紅粉知己的機率一樣低... 謎之聲: 大叔,你病得不輕啊~~)
決定採取先前用過的難字偵測法,取得檔案原始內容(byte[]),利用Encoding.GetString()將byte[]以BIG5編碼解讀成字串,再Encoding.GetBytesCount()將字串還原回byte[]計算長度,若陣列長度與來源陣列長度一致,即表示其中內容完全符合BIG5編碼,即便其非中文內容(例如: 純英數字),使用BIG5編碼處理也不會破壞資料。
以下為程式範例,同時建立UTF-8(無BOM)、UTF-8(含BOM)、Unicode、BIG5、英數字ASCII五種不同編碼檔案進行測試。
using System;
using System.Text;
using System.IO;
namespace DetectEncoding
{
class Program
{
//偵測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));
}
static void Main(string[] args)
{
//準備5個檔案,分別存成UTF-8(無BOM), UTF-8(含BOM), Unicode, BIG5, 英數字
string s = "中文測試";
string[] files = new string[] {
"D:\\UTF8.txt", "D:\\UTF8wBOM.txt", "D:\\Unicode.txt",
"D:\\BIG5.txt", "D:\\ASCII.txt"
};
//WriteAllText(),預設Encoding為不含BOM的UTF-8編碼
File.WriteAllText(files[0], s);
//存為含BOM的UTF-8
File.WriteAllText(files[1], s, new UTF8Encoding(true));
//存為Unicode
File.WriteAllText(files[2], s, Encoding.Unicode);
//存為BIG5
File.WriteAllText(files[3], s, Encoding.GetEncoding(950));
//純英數字,存為ASCII
File.WriteAllText(files[4], "123ABC", Encoding.ASCII);
//測試結果
foreach (string file in files)
{
Console.WriteLine("{0} {1}BIG5編碼.",
Path.GetFileName(file),
IsBig5Encoding(file) ? "相容於" : "不相容於"
);
}
Console.Read();
}
}
}
執行結果如下:
UTF8.txt 不相容於BIG5編碼.
UTF8wBOM.txt 不相容於BIG5編碼.
Unicode.txt 不相容於BIG5編碼.
BIG5.txt 相容於BIG5編碼.
ASCII.txt 相容於BIG5編碼.
Comments
# by Allen Kuo
我開一個檔案,裡面只輸入"中",並存成簡體中文(codepage 936)時,可以通過測試, 看來要再想想是否有其他方式
# by Jeffrey
to Allen Kuo, Encoding.GetEncoding(950).GetString(Encoding.GetEncoding(936).GetBytes("中"))會得到"笢 ",換句話說,從讀取程式的觀點,這個檔案可能是一個存了"笢"字的Big5檔案,也可能是一個存了"中"字的GB2312檔案。二者都有可能也都合理,我個人認為這個情境下,無法判別它是BIG5還是GB2312。
# by Allen Kuo
嗯, 原本我想寫一個可以自動判別簡中或繁中編碼的程式,不過看來無法做到
# by Eric
感謝您,解決了我一個大問題!! Utf-8編碼的檔案如果有BOM還好解決,但沒有BOM的Utf-8就無法判斷是big5還是utf-8了。 剛好這個Function轉換了我的思維,原來還可以用轉換後陣列長度的方式來判斷!!太棒了!!
# by Eric
您好~可否轉載此篇到我的blog呢?想做個筆記~ 會註明出處的,謝謝!!
# by Eric
您好,後來我發現個問題,當utf-8(無BOM)編碼的文字檔內僅有"測試"這兩個中文字時,使用函數判斷是會回傳True的,也就是那六組byte,每兩組都有相對應的Big5編碼文字,所以會誤判。 萬一檔案內所有的byte都有相對應的big5文字的話,此方法就不能用了耶@@
# by Jeffrey
to Eric, 歡迎轉載應用。你所說的誤判狀況是存在的,因UTF-8與BIG5的編碼區間重疊,故此法要達100%精準判斷是不可能的。 所幸,當文字數愈多,其UTF-8編碼要完全相容BIG5的機率就愈低,在我的實務經驗裡,這個極簡便但非絕對精準的做法處理實際資料尚未出錯過(原因是系統所輸入文字檔均有一定長度),加上誤判造成的損失有限,仍可視為有效的解決方案。
# by Eric
Hi Jeffrey大您好: 小弟因公司專案需要處理檔案,但檔案內中文字最少很可能只有三個字而已,非常有可能會誤判,所以自己研究了一下utf-8的規格,發現編碼並不難,所以撰寫了C#版的程式,應該可大幅降低誤判的機率(今日發生過一次,尚在研究中)。 也請不吝指教,小弟發表在 http://eric0806.blogspot.tw/2014/07/detect-file-encoding-utf8.html
# by Jeffrey
to Eric, 謝謝你的回饋分享。 依我淺見,解析UTF-8編碼並無法解決模稜兩可的情境。理由是若有個檔案只有6個Byte,可被解釋成兩個UTF-8字元或三個BIG5字元,則你會得到IsUtf8()及IsBig5()都傳回true的結果,仍無法判斷是哪一種。 我在處理簡繁體自動辨識時遇過極為相似的情境(http://blog.darkthread.net/post-2014-04-27-big5-gb2312-auto-detect.aspx),曾用過另一種輔助解法: 將中文字區分為常用字及罕用字,一般而言,常用字出現的機率高於罕用字。因此,若BIG5解析出的文字為常用字,而UTF-8解出的字為罕用字,我就推測它是BIG5的可能性"比較高",但這些都只能提高猜對的機率,真的遇上原文是罕用字的特殊案例仍會誤判。基本上,要做到絕對精準,我認為無解。
# by 迪納敏斯
HI 最近剛好需要這個東西.. 因為之前VS2013用T4產生的檔案忘記指定編碼變成BIG5 VS2015的編譯器下會有問題 不過我簡單的加了一個 var bytes = File.ReadAllBytes(file); if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) return false; 判斷是否有UTF8 BOM 如此一來"至少" 有BOM的UTF8檔案不會被誤判
# by Jeffrey
to 迪納敏斯,VS2015的BIG5相容問題可以參考我的批次轉檔潛盾機以及免轉檔解法:http://blog.darkthread.net/post-2015-08-12-vs2015-big5-fix.aspx
# by jahochang
StreamReader.CurrentEncoding即可判斷 https://msdn.microsoft.com/en-us/library/system.io.streamreader.currentencoding(v=vs.110).aspx
# by Yohey
黑大您好, 日前處理上有需要使用到您的判斷方式, 完成後做些整理及註解放到小弟的部落格上(有註明出處), (https://yohey66.wordpress.com/2017/07/18/convert-big5-to-utf8/) 在此跟您告知一聲, 也謝謝您分享的方法 :)
# by DeanLai
感謝黑大,解決了最近的一個大問題!! 也是與 Eric 的情況相同,遇到回傳回來的是 NoBom的Utf-8 原來用轉換後陣列長度會不同!!太開心了!!
# by adiot
># 2014-07-14 11:20 PM by Eric >您好,後來我發現個問題,當utf-8(無BOM)編碼的文字檔內僅有"測試"這兩個中文字時,使用函數判斷是會回傳True的,也就是那六組byte,每兩組都有相對應的Big5編>碼文字,所以會誤判。 >萬一檔案內所有的byte都有相對應的big5文字的話,此方法就不能用了耶@@ 再多加一個utf-8字串長度測試: big5長度對而utf-8長度錯=>big5編碼 兩種長度均對=>utf-8編碼 可以進一步排除