NET(C#):關於正確讀取中文編碼文件


https://blog.csdn.net/ma_jiang/article/details/53213442

首先如果讀者對編碼或者BOM還不熟悉的話,推薦先讀這篇文章:.NET(C#):字符編碼(Encoding)和字節順序標記(BOM)

中文編碼基本可以分成兩大類:
1. ANSI編碼的擴展集合:比如GBK, GB2312, GB18030等,這類編碼都不存在BOM(一些更新的標准中文編碼,比如GB18030和GBK編碼,都向后兼容GB2312編碼)。
2. Unicode編碼集合:比如UTF-8, UTF-16, UTF-32等。這類編碼可以有BOM,也可以不加BOM。
3. 部分Unicode編碼還存在具體字節次序問題(Endianess),就是所謂的Little endian和Big endian之分,不同此節次序對於不同的BOM,比如UTF16,不過UTF8不存在字節次序問題。

OK,了解了基本知識后,讓我們回到主題,該如何正確打開中文文本文件。第一個需要確認的信息是:你的Unicode編碼文件是否包含BOM?

如果包含BOM的話,那么一切好說!因為如果我們發現了BOM,我們就知道他的具體編碼了。如果沒有發現BOM,那就不是Unicode,我們用系統默認的ANSI擴展中文編碼集打開文本文件就OK了。
而如果Unicode編碼沒有BOM的話(顯然,你不能保證用戶給你的所有Unicode文件都是有BOM的),那么你要手動從原始字節中判斷他是GBK?還是UTF8?還是其他編碼?。這個就需要具體的編碼覺察算法了(可以google “charset|encoding detection”), 當然編碼覺察算法不一定會100%准確,正是因為這點,Windows記事本會有Bush hid the facts bug。在Chrome瀏覽網頁時,也會遇到亂碼的情況的。個人感覺,Notepad++的編碼覺察做的還是很准確的。
編碼覺察算法有很多,比如這個工程:https://code.google.com/p/ude

 
如果Unicode都帶BOM的話,則就不需要第三方類庫了。不過也有一些需要說明的地方。

問題就是.NET中讀取文本方法(File類和StreamReader)默認是以UTF8編碼來讀取的,因此此類GBK的文本文件直接用.NET打開(不指定編碼的話)結果肯定是亂碼!

首先這里最有效地解決方案是使用系統默認的ANSI擴展編碼,也就是系統默認的非Unicode編碼來讀取文本,參考代碼:

//輸出系統默認非Unicode編碼
Console.WriteLine(Encoding.Default.EncodingName);
//使用系統默認非Unicode編碼來打開文件
var fileContent = File.ReadAllText("C:\test.txt", Encoding.Default);

 

在簡體中文的Windows系統下應該輸出:

簡體中文(GB2312)
<文本內容省略>...

而且使用這個方法其實是不限於簡體中文的。

 

當然也可以手動去指定一個編碼,比如就是GBK編碼,但是如果用指定的GBK編碼去打開一個Unicode文件,文件還會打開成功嗎?答案是仍然成功。原因是.NET在打開文件時默認會自動覺察BOM然后用根據BOM得到的編碼去打開文件,如果沒有BOM再用用戶指定的編碼區打開文件,如果用戶沒有指定編碼,則使用UTF8編碼。

 

這個”自動覺察BOM“的參數可以在StreamReader中構造函數中設置,對應detectEncodingFromByteOrderMarks參數。

但是在File類的相應方法中無法設置。(比如:File.ReadAllText)。

 

比如下面代碼,分別用:

  • GB2312編碼,自動覺察BOM 來讀取GB2312文本
  • GB2312編碼,自動覺察BOM 來讀取Unicode文本
  • GB2312編碼,不覺察BOM 來讀取Unicode文本

 

static void Main()
{
    var gb2312 = Encoding.GetEncoding("GB2312");
    //用GB2312編碼,自動覺察BOM 來讀取GB2312文本
    ReadFile("gbk.txt", gb2312, true);
    //用GB2312編碼,自動覺察BOM 來讀取Unicode文本
    ReadFile("unicode.txt", gb2312, true);
    //用GB2312編碼,不覺察BOM 來讀取Unicode文本
    ReadFile("unicode.txt", gb2312, false);
}

//通過StreamReader讀取文本
 static void ReadFile(string path, Encoding enc, bool detectEncodingFromByteOrderMarks)
{
    StreamReader sr;
    using (sr = new StreamReader(path, enc, detectEncodingFromByteOrderMarks))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

 

輸出:

a劉
a劉
???

第三行是亂碼。

 

看到上面,使用GB2312編碼去打開Unicode文件也會成功的。因為“自動覺察BOM”參數為True,所以當發現該文件有BOM,.NET會通過BOM覺察到是Unicode文件,然后用Unicode去打開文件的。當然如果沒有BOM,會使用指定的編碼參數去打開文件。對於GB2312編碼的文本,顯然是沒有BOM的,所以必須指定GB2312編碼,否則.NET會用默認的UTF8編碼去解析文件,是無法讀取結果的。第三行出現亂碼則是由於“自動覺察BOM”為False,.NET會直接用指定的GB2312編碼去讀取一個有BOM的Unicode編碼文本文件,顯然無法成功的。

 

當然還可以自己判斷BOM,如果沒有BOM的話,指定一個缺省編碼去打開文本。我在以前一篇文章中寫到過(.NET(C#):從文件中覺察編碼)。

代碼:

static void Main()
{
    PrintText("gb2312.txt");
    PrintText("unicode.txt");
}

//根據文件自動覺察編碼並輸出內容
static void PrintText(string path)
{
    var enc = GetEncoding(path, Encoding.GetEncoding("GB2312"));
    using (var sr = new StreamReader(path, enc))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

/// <summary>
/// 根據文件嘗試返回字符編碼
/// </summary>
/// <param name="file">文件路徑</param>
/// <param name="defEnc">沒有BOM返回的默認編碼</param>
/// <returns>如果文件無法讀取,返回null。否則,返回根據BOM判斷的編碼或者缺省編碼(沒有BOM)。</returns>
static Encoding GetEncoding(string file, Encoding defEnc)
{
    using (var stream = File.OpenRead(file))
    {
        //判斷流可讀?
        if (!stream.CanRead)
            return null;
        //字節數組存儲BOM
        var bom = new byte[4];
        //實際讀入的長度
        int readc;

        readc = stream.Read(bom, 0, 4);

        if (readc >= 2)
        {
            if (readc >= 4)
            {
                //UTF32,Big-Endian
                if (CheckBytes(bom, 4, 0x00, 0x00, 0xFE, 0xFF))
                    return new UTF32Encoding(true, true);
                //UTF32,Little-Endian
                if (CheckBytes(bom, 4, 0xFF, 0xFE, 0x00, 0x00))
                    return new UTF32Encoding(false, true);
            }
            //UTF8
            if (readc >= 3 && CheckBytes(bom, 3, 0xEF, 0xBB, 0xBF))
                return new UTF8Encoding(true);

            //UTF16,Big-Endian
            if (CheckBytes(bom, 2, 0xFE, 0xFF))
                return new UnicodeEncoding(true, true);
            //UTF16,Little-Endian
            if (CheckBytes(bom, 2, 0xFF, 0xFE))
                return new UnicodeEncoding(false, true);
        }

        return defEnc;
    }
}

//輔助函數,判斷字節中的值
static bool CheckBytes(byte[] bytes, int count, params int[] values)
{
    for (int i = 0; i < count; i++)
        if (bytes[i] != values[i])
            return false;
    return true;
}

上面代碼,對於Unicode文本,GetEncoding方法會返回UTF16編碼(更具體:還會根據BOM返回Big或者Little-Endian的UTF16編碼),而沒有BOM的文件則會返回缺省值GB2312編碼。

Related Posts:
  1. .NET(C#):從文件中覺察編碼
  2. .NET(C#):字符編碼(Encoding)和字節順序標記(BOM)
  3. .NET(C#):使用System.Text.Decoder類來處理“流文本”
  4. .NET(C#):淺談程序集清單資源和RESX資源


免責聲明!

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



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