• (转载)dotnet core 中文乱码 codepages


    引子

    转载自:http://www.jianshu.com/p/1c9c59c5749a

    参考:.Net Core 控制台输出中文乱码

    上文中我查阅了一些cli的源码, 闲来无事就继续翻代码, 冥冥之中自有天意, 在无尽的代码中, 我看到了这样一个注释

    // by default, .NET Core doesn't have all code pages needed for Console apps.
    // see the .NET Core Notes in https://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx

    dotnet core 团队的思路是好的, 提供一个比较小的程序集, 码农们按自己的需求自行添加需要的依赖, 可以生成更小的程序集, 完全同意. But, 我猜肯定有人坠坑过.

    有人坠么?

    随便google了一下得出以下结果

    果不出我所料, 中奖的兄弟还不少, 不过也早有大神对此事进行过叙述, 并且早已在其文中给出解决方法, 大神的文章在此 难道.NET Core到R2连中文编码都不支持吗?(http://www.cnblogs.com/artech/archive/2016/05/18/5507092.html)
    有人会问了, 那你还写个屁文干啥?

    事还没完!

    为啥没完? 因为看到大神文章的评论中一个兄弟在坑中还没爬上来. 川酷不能见死不救是不是?
    据那位仁兄所说, 事情的经过是这样的.

            Console.WriteLine("你好, 世界!");
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            Console.WriteLine("你好, 世界!");

    按大神文章所述, 注册了CodePages 编码则为UTF-8, 中文不应该乱码, 可事实是

    难道大神也错了吗? 当然不会!

            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

    正常情况下这句代码足矣解决问题, 依cli注释所说, 对于Console程序, 并没有添加所有的code page, 而且注释中给出的微软官方解决方案也是添加codepage 的dll程序集, 并添加上面那句代码.
    这个时候我又找到了另外一个大神的blog,里面有另一种解决方案

        public static void Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.UTF8;//第一种方式:指定编码
            //Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);//第二种方式
    
            Console.Read();
        }

    既然有方案一, 川酷肯定要试一下.

            Console.WriteLine("你好, 世界!");
            Console.OutputEncoding=Encoding.UTF8;
            //Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            Console.WriteLine("你好, 世界!");

    大神就是大神, 这招灵!

    不过第二行的输出有一点小问题, 有兴趣的童鞋研究一下, 在此不再赘述.

    让我带你飞!

    依川酷的风格, 翻代码的时候到了! 既然问题出在Console中, 那就找Console的代码来看下. 有兴趣的可以在这里获取代码 . 让我们找到Console.cs这个文件, 可以看到相应的WriteLine方法的重载.

        [MethodImplAttribute(MethodImplOptions.NoInlining)]
        public static void WriteLine(String value)
        {
            Out.WriteLine(value);
        }

    再看Out是个什么鬼.

        public static TextWriter Out
        {
            get { return Volatile.Read(ref s_out) ?? EnsureInitialized(ref s_out, () => CreateOutputWriter(OpenStandardOutput())); }
        }

    然后我们发现s_out就是用于输出的TextWriter.
    并且在第一次调用的时候会创建一个新的.

        private static TextWriter CreateOutputWriter(Stream outputStream)
        {
            return SyncTextWriter.GetSynchronizedTextWriter(outputStream == Stream.Null ?
                StreamWriter.Null :
                new StreamWriter(
                    stream: outputStream,
                    encoding: new ConsoleEncoding(OutputEncoding), // This ensures no prefix is written to the stream.
                    bufferSize: DefaultConsoleBufferSize,
                    leaveOpen: true) { AutoFlush = true });
        }

    这样看来TextWriter的Encoding是使用的OutputEncoding, 那我们实践下, 这个初始的OutputEncoding到底是什么.

    既然是UTF-8为何还是乱码? 此事还应该从微软的那句注释说起, 人家说了, 默认是没有添加codepage的, 即使你现在是UTF-8编码, 但是程序中没有codepage, 当然没办法处理. 我们来看看OutputEncoding的源码吧.

        public static Encoding OutputEncoding
        {
            get
            {
                return Volatile.Read(ref s_outputEncoding) ?? EnsureInitialized(ref s_outputEncoding, () => ConsolePal.OutputEncoding);
            }
            set
            {
                CheckNonNull(value, "value");
    
                lock (InternalSyncObject)
                {
                    // Set the terminal console encoding.
                    ConsolePal.SetConsoleOutputEncoding(value);
    
                    // Before changing the code page we need to flush the data 
                    // if Out hasn't been redirected. Also, have the next call to  
                    // s_out reinitialize the console code page.
                    if (Volatile.Read(ref s_out) != null && !s_isOutTextWriterRedirected)
                    {
                        s_out.Flush();
                        Volatile.Write(ref s_out, null);
                    }
                    if (Volatile.Read(ref s_error) != null && !s_isErrorTextWriterRedirected)
                    {
                        s_error.Flush();
                        Volatile.Write(ref s_error, null);
                    }
    
                    Volatile.Write(ref s_outputEncoding, (Encoding)value.Clone());
                }
            }
        }

    我猜问题就出在了ConsolePal.SetConsoleOutputEncoding(value) 这一句.
    我相信大家记得, 在我显性set了OutputEncoding为UTF-8之后控制台的中文显示就正常了. 也就是说对OutputEncoding做了set动作之后, 会强制Console窗口引入codepage文件.

    Summary

    比较一下两种解决方式, 其实两者有本质的不同, SetConsoleOutputEncoding是为这个控制台实例做编码的设置, 而 RegisterProvider 是为当前这个程序集添加codepage.
    之所以 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)不起作用是因为前面的一句Console.WriteLine("你好, 世界!")已经使该命令行窗口初始化了编码. 执行Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)并不对命令行窗口起作用, 在这里给做一个实验, 我相信大家就可以清楚中间到底发生了什么.
    首先, 重复刚开始的代码.

            Console.WriteLine("你好, 世界!");
            // Console.OutputEncoding=Encoding.UTF8;
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            Console.WriteLine("你好, 世界!");

    得到的结果是这样的.

    然后为该命令行执行这句Console.OutputEncoding=Encoding.UTF8;. 再看下结果.


    其实此时的RegisterProvider方法已经没有意义了, 因为命令行已经加载了codepage. 就是这样, 完结!

    欢迎大家讨论, 如果有觉得不对或不妥的地方也希望大家可以指正, 我会努力写一些高质量的文章.

  • 相关阅读:
    [BZOJ 4318] OSU!
    [BZOJ 4720][NOIP 2016] 换教室
    [Tyvj 1729] 文艺平衡树
    [BZOJ 1500]维修数列 [Splay Tree从进阶到住院]
    [学习笔记] CDQ分治 从感性理解到彻底晕菜
    [COGS 1752] 摩基亚Mokia
    [Tyvj 1730] 二逼平衡树
    [学习笔记] Splay Tree 从入门到放弃
    [Tyvj 1728] 普通平衡树
    [BZOJ 3594] 方伯伯的玉米田
  • 原文地址:https://www.cnblogs.com/mailaidedt/p/7054984.html
Copyright © 2020-2023  润新知