【问题标题】:How can I detect the encoding/codepage of a text file如何检测文本文件的编码/代码页
【发布时间】:2010-09-10 14:08:02
【问题描述】:

在我们的应用程序中,我们接收来自不同来源的文本文件(.txt.csv 等)。读取时,这些文件有时包含垃圾,因为这些文件是在不同/未知的代码页中创建的。

有没有办法(自动)检测文本文件的代码页?

StreamReader 构造函数上的detectEncodingFromByteOrderMarks 适用于UTF8 和其他带有unicode 标记的文件,但我正在寻找一种检测代码页的方法,例如ibm850windows1252


感谢您的回答,这就是我所做的。

我们收到的文件来自最终用户,他们对代码页一无所知。接收者也是最终用户,到目前为止,这就是他们对代码页的了解:代码页存在,而且很烦人。

解决方案:

  • 在记事本中打开收到的文件,查看一段乱码。如果有人叫弗朗索瓦什么的,以你的人类智慧你可以猜到。
  • 我创建了一个小应用程序,用户可以使用它打开文件,并输入用户知道在使用正确代码页时会出现在文件中的文本。
  • 遍历所有代码页,并使用用户提供的文本显示提供解决方案的代码页。
  • 如果弹出更多作为一个代码页,请让用户指定更多文本。

【问题讨论】:

    标签: c# .net text encoding globalization


    【解决方案1】:

    自从提出这个问题以来已经过去了 10 年(!),但我仍然没有看到任何提及 MS 良好的非 GPL 解决方案:IMultiLanguage2 API。

    已经提到的大多数库都是基于 Mozilla 的 UDE - 浏览器已经解决了类似的问题似乎是合理的。我不知道chrome的解决方案是什么,但是自从IE 5.0 MS发布了他们的解决方案,它是:

    1. 没有 GPL 和类似的许可问题,
    2. 可能永远得到支持和维护,
    3. 提供丰富的输出 - 所有有效的候选编码/代码页以及置信度分数,
    4. 非常容易使用(它是一个函数调用)。

    这是一个本机 COM 调用,但 here's some very nice work 由 Carsten Zeumer 编写,用于处理 .net 使用的互操作混乱。还有一些其他的,但总的来说,这个库并没有得到应有的关注。

    【讨论】:

      【解决方案2】:

      感谢@Erik Aronesty 提及uchardet

      同时(相同的?)工具存在于 linux 上:chardet
      或者,在 cygwin 上,您可能想要使用:chardetect

      见:chardet man page: https://www.commandlinux.com/man-page/man1/chardetect.1.html

      这将启发式检测(猜测)每个给定文件的字符编码,并将报告每个文件检测到的字符编码的名称和置信度。

      【讨论】:

        【解决方案3】:

        作为 ITmeze 帖子的插件,我使用此函数将 C# 端口的输出转换为 Mozilla Universal Charset Detector

            private Encoding GetEncodingFromString(string codePageName)
            {
                try
                {
                    return Encoding.GetEncoding(codePageName);
                }
                catch
                {
                    return Encoding.ASCII;
                }
            }
        

        MSDN

        【讨论】:

          【解决方案4】:

          如果您希望检测非 UTF 编码(即没有 BOM),则基本上可以对文本进行启发式和统计分析。您可能想看看Mozilla paper on universal charset detection (same link, with better formatting via Wayback Machine)。

          【讨论】:

          • 有趣的是,我的 Firefox 3.05 安装检测到该页面为 UTF-8,显示许多问号-菱形字形,尽管源具有 Windows-1252 的元标记。手动更改字符编码可以正确显示文档。
          • 您的句子“如果您要检测非 UTF 编码(即没有 BOM)”有点误导; unicode 标准不建议向 utf-8 文档添加 BOM! (这个建议,或者缺乏这个建议,是许多头痛的根源)。参考:en.wikipedia.org/wiki/Byte_order_mark#UTF-8
          • 这样做是为了您可以连接 UTF-8 字符串而不会累积冗余 BOM。此外,与 UTF-16 不同,UTF-8 不需要字节顺序标记。
          【解决方案5】:

          在 AkelPad 中打开文件(或者只是复制/粘贴一个乱码),转到编辑 -> 选择 -> 重新编码... -> 选中“自动检测”。

          【讨论】:

            【解决方案6】:

            如果有人正在寻找 93.9% 的解决方案。这对我有用:

            public static class StreamExtension
            {
                /// <summary>
                /// Convert the content to a string.
                /// </summary>
                /// <param name="stream">The stream.</param>
                /// <returns></returns>
                public static string ReadAsString(this Stream stream)
                {
                    var startPosition = stream.Position;
                    try
                    {
                        // 1. Check for a BOM
                        // 2. or try with UTF-8. The most (86.3%) used encoding. Visit: http://w3techs.com/technologies/overview/character_encoding/all/
                        var streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true);
                        return streamReader.ReadToEnd();
                    }
                    catch (DecoderFallbackException ex)
                    {
                        stream.Position = startPosition;
            
                        // 3. The second most (6.7%) used encoding is ISO-8859-1. So use Windows-1252 (0.9%, also know as ANSI), which is a superset of ISO-8859-1.
                        var streamReader = new StreamReader(stream, Encoding.GetEncoding(1252));
                        return streamReader.ReadToEnd();
                    }
                }
            }
            

            【讨论】:

            • 非常好的解决方案。如果应该允许超过 2 种编码(UTF-8 和 ASCI 1252),则可以轻松地将 ReadAsString() 的主体包装在允许的编码循环中。
            • 在尝试了大量示例之后,我终于找到了你的示例。我现在在一个快乐的地方。大声笑谢谢!!!!!!!
            • 这可能不是如何检测 1252 与 1250 的答案,但它绝对应该是“如何检测 UTF-8”有或没有 BOM 的答案!
            • @chuckc is 没有合适的方法来检测不同的无 BOM 每符号一个字节的编码。在那个级别上,你完全是在启发式的。
            【解决方案7】:

            我在读取文件时使用此代码检测 Unicode 和 windows 默认 ansi 代码页。对于其他编码,需要手动或通过编程检查内容。这可以用来以与打开时相同的编码保存文本。 (我用的是 VB.NET)

            'Works for Default and unicode (auto detect)
            Dim mystreamreader As New StreamReader(LocalFileName, Encoding.Default) 
            MyEditTextBox.Text = mystreamreader.ReadToEnd()
            Debug.Print(mystreamreader.CurrentEncoding.CodePage) 'Autodetected encoding
            mystreamreader.Close()
            

            【讨论】:

              【解决方案8】:

              您无法检测到代码页

              这显然是错误的。每个网络浏览器都有某种通用字符集检测器来处理没有任何编码指示的页面。火狐有一个。你可以下载代码,看看它是怎么做的。请参阅一些文档here。基本上,它是一种启发式方法,但效果非常好。

              给定合理数量的文本,甚至可以检测语言。

              Here's another one我刚用谷歌发现的:

              【讨论】:

              • "heuristics" - 所以浏览器并没有完全检测到它,它正在做出有根据的猜测。 “工作得很好” - 所以它不是一直有效吗?在我看来,我们意见一致。
              • HTML 标准规定,如果文档未定义字符集,则应将其视为编码为 UTF-8。
              • 这很酷,除非我们正在阅读非标准的 HTML 文档。或非 HTML 文档。
              • 这个答案是错误的,所以我不得不投反对票。说您无法检测到代码页是错误的,这是错误的。您可以猜测,而且您的猜测可能相当好,但您无法“检测”代码页。
              • @JonTrauntvein 根据HTML5 specs a character encoding declaration is required even if the encoding is US-ASCII - 缺少声明会导致使用启发式算法,而不是退回到UTF8。
              【解决方案9】:

              “uchardet”工具可以很好地使用每个字符集的字符频率分布模型。更大的文件和更“典型”的文件更有信心(显然)。

              在 ubuntu 上,您只需 apt-get install uchardet

              在其他系统上,请在此处获取源代码、用法和文档:https://github.com/BYVoid/uchardet

              【讨论】:

              • 在 Mac 上通过自制软件:brew install uchardet
              【解决方案10】:

              寻找不同的解决方案,我发现

              https://code.google.com/p/ude/

              这个解决方案有点重。

              我需要一些基本的编码检测,基于 4 个首字节和可能的 xml 字符集检测 - 所以我从互联网上获取了一些示例源代码并添加了稍微修改的版本

              http://lists.w3.org/Archives/Public/www-validator/2002Aug/0084.html

              为 Java 编写。

                  public static Encoding DetectEncoding(byte[] fileContent)
                  {
                      if (fileContent == null)
                          throw new ArgumentNullException();
              
                      if (fileContent.Length < 2)
                          return Encoding.ASCII;      // Default fallback
              
                      if (fileContent[0] == 0xff
                          && fileContent[1] == 0xfe
                          && (fileContent.Length < 4
                              || fileContent[2] != 0
                              || fileContent[3] != 0
                              )
                          )
                          return Encoding.Unicode;
              
                      if (fileContent[0] == 0xfe
                          && fileContent[1] == 0xff
                          )
                          return Encoding.BigEndianUnicode;
              
                      if (fileContent.Length < 3)
                          return null;
              
                      if (fileContent[0] == 0xef && fileContent[1] == 0xbb && fileContent[2] == 0xbf)
                          return Encoding.UTF8;
              
                      if (fileContent[0] == 0x2b && fileContent[1] == 0x2f && fileContent[2] == 0x76)
                          return Encoding.UTF7;
              
                      if (fileContent.Length < 4)
                          return null;
              
                      if (fileContent[0] == 0xff && fileContent[1] == 0xfe && fileContent[2] == 0 && fileContent[3] == 0)
                          return Encoding.UTF32;
              
                      if (fileContent[0] == 0 && fileContent[1] == 0 && fileContent[2] == 0xfe && fileContent[3] == 0xff)
                          return Encoding.GetEncoding(12001);
              
                      String probe;
                      int len = fileContent.Length;
              
                      if( fileContent.Length >= 128 ) len = 128;
                      probe = Encoding.ASCII.GetString(fileContent, 0, len);
              
                      MatchCollection mc = Regex.Matches(probe, "^<\\?xml[^<>]*encoding[ \\t\\n\\r]?=[\\t\\n\\r]?['\"]([A-Za-z]([A-Za-z0-9._]|-)*)", RegexOptions.Singleline);
                      // Add '[0].Groups[1].Value' to the end to test regex
              
                      if( mc.Count == 1 && mc[0].Groups.Count >= 2 )
                      {
                          // Typically picks up 'UTF-8' string
                          Encoding enc = null;
              
                          try {
                              enc = Encoding.GetEncoding( mc[0].Groups[1].Value );
                          }catch (Exception ) { }
              
                          if( enc != null )
                              return enc;
                      }
              
                      return Encoding.ASCII;      // Default fallback
                  }
              

              从文件中读取前 1024 个字节就足够了,但我正在加载整个文件。

              【讨论】:

                【解决方案11】:

                我实际上是在寻找一种通用的、非编程的方式来检测文件编码,但我还没有找到。 通过使用不同编码进行测试,我确实发现我的文本是 UTF-7。

                所以我第一次做的地方是: StreamReader 文件 = File.OpenText(fullfilename);

                我不得不将其更改为: StreamReader file = new StreamReader(fullfilename, System.Text.Encoding.UTF7);

                OpenText 假定它是 UTF-8。

                你也可以像这样创建 StreamReader new StreamReader(fullfilename, true),第二个参数意味着它应该尝试从文件的字节序标记中检测编码,但这在我的情况下不起作用。

                【讨论】:

                • @JohnMachin 我同意这种情况很少见,但它是强制性的,例如在 IMAP 协议的某些部分。不过,如果这就是你所在的地方,你就不必猜测了。
                【解决方案12】:

                您无法检测到代码页,您需要被告知。您可以分析字节并猜测它,但这可能会产生一些奇怪(有时很有趣)的结果。我现在找不到它,但我确信记事本可以被欺骗以中文显示英文文本。

                无论如何,这是您需要阅读的内容: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!).

                乔尔特别说:

                关于编码的最重要的事实

                如果您完全忘记了我刚刚解释的所有内容,请记住一个极其重要的事实。在不知道它使用什么编码的情况下拥有一个字符串是没有意义的。你不能再把头埋在沙子里,假装“纯”文本是 ASCII。 没有纯文本这样的东西。

                如果您在内存、文件或电子邮件中有一个字符串,您必须知道它的编码是什么,否则您无法正确解释它或将其显示给用户。

                【讨论】:

                • 我对这个答案投了反对票,原因有两个。首先,说“你需要被告知”是没有帮助的。谁会告诉我,他们会通过什么媒介这样做?如果我是保存文件的人,我会问谁?我?其次,这篇文章作为回答问题的资源并不是特别有用。这篇文章更像是一段以 David Sedaris 风格编写的编码历史。我很欣赏这种叙述,但它并不能简单/直接地回答问题。
                • @geneorama,我认为 Joel 的文章比以往任何时候都更好地解决了您的问题,但是这里……媒介肯定取决于接收文本的环境。最好是文件(或其他)包含该信息(我在想 HTML 和 XML)。否则,应允许发送文本的人提供该信息。如果你是创建文件的人,你怎么会不知道它使用的是什么编码?
                • @geneorama,继续... 最后,我想这篇文章没有回答这个问题的主要原因是因为这个问题没有简单的答案。如果问题是“我怎么能猜到...”,那么我的回答会有所不同。
                • @JV 后来了解到xml/html可以指定字符编码,谢谢提那个有用的花絮。
                • @JV“创建文件”可能是一个糟糕的词选择。我假设用户可以指定用户生成的文件的编码。最近,我使用 Hive 从 Hadoop 集群“创建”了一个文件,并将其传递到 FTP,然后再将其下载到各种客户端机器。结果中有一些 unicode 垃圾,但我不知道是哪一步造成了问题。我从未明确指定编码。我希望我可以在每一步检查编码。
                【解决方案13】:

                如果您可以链接到 C 库,则可以使用 libenca。见http://cihar.com/software/enca/。从手册页:

                Enca 读取给定的文本文件,或在没有给出标准输入时读取, 并使用有关其语言的知识(必须得到您的支持)和 解析、统计分析、猜测和黑魔法的混合体 来确定它们的编码。

                这是 GPL v2。

                【讨论】:

                  【解决方案14】:

                  你试过C# port for Mozilla Universal Charset Detector

                  来自http://code.google.com/p/ude/的示例

                  public static void Main(String[] args)
                  {
                      string filename = args[0];
                      using (FileStream fs = File.OpenRead(filename)) {
                          Ude.CharsetDetector cdet = new Ude.CharsetDetector();
                          cdet.Feed(fs);
                          cdet.DataEnd();
                          if (cdet.Charset != null) {
                              Console.WriteLine("Charset: {0}, confidence: {1}", 
                                   cdet.Charset, cdet.Confidence);
                          } else {
                              Console.WriteLine("Detection failed.");
                          }
                      }
                  }    
                  

                  【讨论】:

                  • 完美适用于 Windows-1252 类型。
                  • 你如何使用它来读取文本文件到字符串? CharsetDetector 以字符串格式返回编码的名称,就是这样......
                  • @Bartosz private Encoding GetEncodingFromString(string encoding) { try { return Encoding.GetEncoding(encoding); } catch { return Encoding.ASCII; } }
                  【解决方案15】:

                  我知道这个问题已经很晚了,而且这个解决方案不会吸引一些人(因为它以英语为中心的偏见和缺乏统计/实证测试),但它对我来说效果很好,尤其是对于处理上传的CSV 数据:

                  http://www.architectshack.com/TextFileEncodingDetector.ashx

                  优点:

                  • 内置 BOM 检测
                  • 可自定义默认/后备编码
                  • 非常可靠(根据我的经验)对于基于西欧的文件,其中包含一些异国情调的数据(例如法语名称)以及 UTF-8 和 Latin-1 样式文件的混合 - 基本上是美国和西欧环境的大部分.

                  注意:我是写这门课的人,所以显然对它持保留态度! :)

                  【讨论】:

                    【解决方案16】:

                    Notepad++ 具有开箱即用的此功能。它还支持更改它。

                    【讨论】:

                      【解决方案17】:

                      由于它基本上归结为启发式方法,因此使用以前从同一来源接收到的文件的编码作为第一个提示可能会有所帮助。

                      大多数人(或应用程序)每次都以几乎相同的顺序执行操作,通常是在同一台机器上,因此当 Bob 创建一个 .csv 文件并将其发送给 Mary 时,它很可能总是使用 Windows -1252 或任何他的机器默认设置。

                      在可能的情况下,一点客户培训也不会受到伤害:-)

                      【讨论】:

                        【解决方案18】:

                        我在 Python 中做过类似的事情。基本上,您需要来自各种编码的大量样本数据,这些数据由滑动的两字节窗口分解并存储在字典(散列)中,以提供编码列表值的字节对为键。

                        给定字典(散列),您将输入文本并:

                        • 如果它以任何 BOM 字符开头('\xfe\xff' 用于 UTF-16-BE,'\xff\xfe' 用于 UTF-16-LE,'\xef\xbb\xbf' 用于 UTF-8 等),我按照建议对待它
                        • 如果不是,则获取足够大的文本样本,获取样本的所有字节对,然后选择字典中建议的最不常见的编码。

                        如果您还对以任何 BOM 开头的 UTF 编码文本进行了采样,则第二步将覆盖那些从第一步中漏掉的文本。

                        到目前为止,它对我有用(示例数据和后续输入数据是各种语言的字幕),并且错误率越来越低。

                        【讨论】:

                          【解决方案19】:

                          遇到了同样的问题,但还没有找到自动检测的好解决方案。 现在我使用 PsPad (www.pspad.com) ;) 工作正常

                          【讨论】:

                            【解决方案20】:

                            StreamReader 类的构造函数采用“检测编码”参数。

                            【讨论】:

                            • 这里只是“编码”link.. 描述说我们必须提供编码..
                            • @SurajS:看看其他的重载。
                            • 原作者想要检测文件的编码,该文件可能没有 BOM 标记。 StreamReader 根据签名检测来自 BOM Header 的编码。 public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
                            猜你喜欢
                            • 1970-01-01
                            • 2011-05-30
                            • 2021-12-30
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多