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/udegoogle

 
若是Unicode都帶BOM的話,則就不須要第三方類庫了。不過也有一些須要說明的地方。編碼

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

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

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

 

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

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

並且使用這個方法實際上是不限於簡體中文的。ip

 

固然也能夠手動去指定一個編碼,好比就是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資源
相關文章
相關標籤/搜索