CODE-偵測檔案是否為BIG5編碼

有個偵測文字檔是否為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編碼.

歡迎推文分享:
Published 11 April 2012 07:38 AM 由 Jeffrey
Filed under:
Views: 29,437



意見

# Allen Kuo said on 04 May, 2012 08:30 AM

我開一個檔案,裡面只輸入"中",並存成簡體中文(codepage 936)時,可以通過測試, 看來要再想想是否有其他方式

# Jeffrey said on 05 May, 2012 02:26 AM

to Allen Kuo, Encoding.GetEncoding(950).GetString(Encoding.GetEncoding(936).GetBytes("中"))會得到"笢

",換句話說,從讀取程式的觀點,這個檔案可能是一個存了"笢"字的Big5檔案,也可能是一個存了"中"字的GB2312檔案。二者都有可能也都合理,我個人認為這個情境下,無法判別它是BIG5還是GB2312。

# Allen Kuo said on 05 May, 2012 09:48 AM

嗯, 原本我想寫一個可以自動判別簡中或繁中編碼的程式,不過看來無法做到

# Eric said on 14 July, 2014 01:29 AM

感謝您,解決了我一個大問題!!

Utf-8編碼的檔案如果有BOM還好解決,但沒有BOM的Utf-8就無法判斷是big5還是utf-8了。

剛好這個Function轉換了我的思維,原來還可以用轉換後陣列長度的方式來判斷!!太棒了!!

# Eric said on 14 July, 2014 10:28 PM

您好~可否轉載此篇到我的blog呢?想做個筆記~

會註明出處的,謝謝!!

# Eric said on 14 July, 2014 11:20 PM

您好,後來我發現個問題,當utf-8(無BOM)編碼的文字檔內僅有"測試"這兩個中文字時,使用函數判斷是會回傳True的,也就是那六組byte,每兩組都有相對應的Big5編碼文字,所以會誤判。

萬一檔案內所有的byte都有相對應的big5文字的話,此方法就不能用了耶@@

# Jeffrey said on 15 July, 2014 01:12 AM

to Eric, 歡迎轉載應用。你所說的誤判狀況是存在的,因UTF-8與BIG5的編碼區間重疊,故此法要達100%精準判斷是不可能的。

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

# Eric said on 16 July, 2014 10:26 AM

Hi Jeffrey大您好:

小弟因公司專案需要處理檔案,但檔案內中文字最少很可能只有三個字而已,非常有可能會誤判,所以自己研究了一下utf-8的規格,發現編碼並不難,所以撰寫了C#版的程式,應該可大幅降低誤判的機率(今日發生過一次,尚在研究中)。

也請不吝指教,小弟發表在 eric0806.blogspot.tw/.../detect-file-encoding-utf8.html

# Jeffrey said on 16 July, 2014 06:10 PM

to Eric, 謝謝你的回饋分享。

依我淺見,解析UTF-8編碼並無法解決模稜兩可的情境。理由是若有個檔案只有6個Byte,可被解釋成兩個UTF-8字元或三個BIG5字元,則你會得到IsUtf8()及IsBig5()都傳回true的結果,仍無法判斷是哪一種。

我在處理簡繁體自動辨識時遇過極為相似的情境(blog.darkthread.net/post-2014-04-27-big5-gb2312-auto-detect.aspx),曾用過另一種輔助解法: 將中文字區分為常用字及罕用字,一般而言,常用字出現的機率高於罕用字。因此,若BIG5解析出的文字為常用字,而UTF-8解出的字為罕用字,我就推測它是BIG5的可能性"比較高",但這些都只能提高猜對的機率,真的遇上原文是罕用字的特殊案例仍會誤判。基本上,要做到絕對精準,我認為無解。

# 迪納敏斯 said on 27 August, 2015 03:35 AM

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檔案不會被誤判

# Jeffrey said on 27 August, 2015 08:39 AM

to 迪納敏斯,VS2015的BIG5相容問題可以參考我的批次轉檔潛盾機以及免轉檔解法:blog.darkthread.net/post-2015-08-12-vs2015-big5-fix.aspx

# jahochang said on 21 March, 2017 05:33 AM

StreamReader.CurrentEncoding即可判斷

msdn.microsoft.com/.../system.io.streamreader.currentencoding(v=vs.110).aspx

# Yohey said on 18 July, 2017 01:57 AM

黑大您好,

日前處理上有需要使用到您的判斷方式,

完成後做些整理及註解放到小弟的部落格上(有註明出處),

(yohey66.wordpress.com/.../convert-big5-to-utf8)

在此跟您告知一聲,

也謝謝您分享的方法 :)

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<April 2012>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication