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编码。