zoukankan      html  css  js  c++  java
  • (转载)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. 就是这样, 完结!

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

  • 相关阅读:
    PTA —— 基础编程题目集 —— 函数题 —— 61 简单输出整数 (10 分)
    PTA —— 基础编程题目集 —— 函数题 —— 61 简单输出整数 (10 分)
    练习2.13 不用库函数,写一个高效计算ln N的C函数
    练习2.13 不用库函数,写一个高效计算ln N的C函数
    练习2.13 不用库函数,写一个高效计算ln N的C函数
    迷宫问题 POJ 3984
    UVA 820 Internet Bandwidth (因特网带宽)(最大流)
    UVA 1001 Say Cheese(奶酪里的老鼠)(flod)
    UVA 11105 Semiprime Hnumbers(H半素数)
    UVA 557 Burger(汉堡)(dp+概率)
  • 原文地址:https://www.cnblogs.com/mailaidedt/p/7054984.html
Copyright © 2011-2022 走看看