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)。
好比下面代碼,分別用:
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編碼。