.NET(C#):关于正确读取中文文本文件

当你用Windows记事本保存文本文件时,默认会使用ANSI编码保存,如果文本包含中文的话,会用GB18030标准编码(GB18030编码向后对GBK编码保持兼容,同时GBK也向后兼容GB2312编码)。

 

在记事本中输入“a刘”,然后保存,用二进制编辑器打开文本文件,结果会是:

61 C1 F5

 

可以看到,61是”a”的ASCII码:97。而C1 F5正是“刘”的GB18030(GBK)码。而整个文件两个字符,英文1个字节,中文两个字节,使用GB18030作为ANSI扩展编码存储。当然也就没有字节顺序标记(BOM)。

 

问题就是.NET中读取文本方法(File类和StreamReader)默认是以UTF8编码来读取的,因此此类GB18030的文本文件直接用.NET打开(不指定编码的话)结果肯定是乱码!

 

当然解决方案之一就是在打开文件的时候手动指定一个编码,比如GB18030,但是有没有想过用指定的GB18030去打开文件,如果遇到了Unicode文件,文件还会打开成功吗?答案是仍然成功。原因是.NET在打开文件时默认会自动觉察BOM然后用根据BOM得到的编码去打开文件,如果没有BOM再用用户指定的编码区打开文件,如果用户没有指定编码,则使用UTF8编码

 

这个”自动觉察BOM“的参数可以在StreamReader中构造函数中设置,对应detectEncodingFromByteOrderMarks参数。

但是在File类的相应方法中无法设置。(比如:File.ReadAllText)。

 

比如下面代码,分别用:

  • GB18030编码,自动觉察BOM 来读取GB18030文本
  • GB18030编码,自动觉察BOM 来读取Unicode文本
  • GB18030编码,不觉察BOM 来读取Unicode文本

 

        static void Main()

        {

            var gb18030 = Encoding.GetEncoding("GB18030");

            //用GB18030编码,自动觉察BOM 来读取GB18030文本

            ReadFile("gbk.txt", gb18030, true);

            //用G18030编码,自动觉察BOM 来读取Unicode文本

            ReadFile("unicode.txt", gb18030, true);

            //用G18030编码,不觉察BOM 来读取Unicode文本

            ReadFile("unicode.txt", gb18030, 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刘

???

第三行是乱码。

 

看到上面,使用GB18030编码去打开Unicode文件也会成功的。因为“自动觉察BOM”参数为True,所以当发现该文件有BOM,.NET会通过BOM觉察到是Unicode文件,然后用Unicode去打开文件的。当然如果没有BOM,会使用指定的编码参数去打开文件。对于GB18030编码的文本,显然是没有BOM的,所以必须指定GB18030编码,否则.NET会用默认的UTF8编码去解析文件,是无法读取结果的。第三行出现乱码则是由于“自动觉察BOM”为False,.NET会直接用指定的GB18030编码去读取一个有BOM的Unicode编码文本文件,显然无法成功的。

 

 

当然还可以自己判断BOM,如果没有BOM的话,指定一个缺省编码去打开文本。我在以前一篇文章中写到过(.NET(C#):从文件中觉察编码)。

 

代码:

        static void Main()

        {

            PrintText("gbk.txt");

            PrintText("unicode.txt");

        }

 

        //根据文件自动觉察编码并输出内容

        static void PrintText(string path)

        {

            var enc = GetEncoding(path, Encoding.GetEncoding("GB18030"));

            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, 04);

 

                if (readc >= 2)

                {

                    if (readc >= 4)

                    {

                        //UTF32,Big-Endian

                        if (CheckBytes(bom, 40x000x000xFE0xFF))

                            return new UTF32Encoding(truetrue);

                        //UTF32,Little-Endian

                        if (CheckBytes(bom, 40xFF0xFE0x000x00))

                            return new UTF32Encoding(falsetrue);

                    }

                    //UTF8

                    if (readc >= 3 && CheckBytes(bom, 30xEF0xBB0xBF))

                        return new UTF8Encoding(true);

 

                    //UTF16,Big-Endian

                    if (CheckBytes(bom, 20xFE0xFF))

                        return new UnicodeEncoding(truetrue);

                    //UTF16,Little-Endian

                    if (CheckBytes(bom, 20xFF0xFE))

                        return new UnicodeEncoding(falsetrue);

                }

 

                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的文件则会返回缺省值GB18030编码。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值