如何检测文本文件的编码/代码页


295

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

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

detectEncodingFromByteOrderMarks,对StreamReader构造,工程UTF8 和其他的Unicode标文件,但是我正在寻找一种方法来检测代码页,像ibm850windows1252


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

我们收到的文件来自最终用户,他们不了解代码页。接收者也是最终用户,到目前为止,这是他们对代码页的了解:代码页存在并且令人讨厌。

解:

  • 在记事本中打开收到的文件,查看乱码的文本。如果有人叫弗朗索瓦(François)之类的东西,凭着您的智慧,您就可以猜到。
  • 我创建了一个小应用程序,用户可用来打开文件,并输入一个文本,用户知道使用正确的代码页时它将出现在文件中。
  • 循环浏览所有代码页,并使用用户提供的文本显示提供解决方案的代码页。
  • 如果弹出一个以上的代码页,请要求用户指定更多文本。

Answers:


260

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

无论如何,这就是您需要阅读的内容: 每个软件开发人员绝对,肯定地必须绝对了解Unicode和字符集(无借口!)

特别是乔尔说:

关于编码的最重要的事实

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

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


43
我拒绝这个答案有两个原因。首先,说“您需要被告知”并没有帮助。谁会告诉我,他们会通过哪种媒介?如果我是保存文件的人,我会问谁?我?其次,本文作为回答问题的资源并不是特别有用。本文更多地是以David Sedaris风格编写的编码历史。我很喜欢这种叙述,但它不能简单/直接回答问题。
2013年

9
@geneorama,我认为Joel的文章比以往任何时候都可以更好地解决您的问题,但是这里...媒介肯定取决于接收文本的环境。最好是文件(或其他文件)包含该信息(我在考虑HTML和XML)。否则,应允许发送文本的人提供该信息。如果您是创建文件的人,那么您怎么不知道它使用什么编码?
合资企业。

4
@geneorama,继续...最后,我想这篇文章不能简单地回答问题的主要原因是因为没有对该问题的简单回答。如果问题是“我怎么猜...”,那么我会回答不同。
合资企业。

1
@JV我后来了解到xml / html可以指定字符编码,这要感谢提到了有用的提示。
2013年

1
@JV“创建文件”可能是一个不好的选择。我假设用户可以指定用户生成的文件的编码。最近,我使用Hive从Hadoop集群“创建”了一个文件,并将其传递到FTP,然后再将其下载到各种客户端计算机。结果中有一些unicode垃圾,但我不知道是哪个步骤造成了问题。我从未明确指定编码。我希望我可以在每个步骤中检查编码。
2013年

31

如果您要检测非UTF编码(即没有BOM),则基本上可以进行文本的启发式和统计分析。您可能想看一下有关通用字符集检测Mozilla论文相同的链接,通过Wayback Machine进行更好的格式化)。


9
有趣的是,我的Firefox 3.05安装将该页面检测为UTF-8,显示了许多钻石问号标志符号,尽管该源代码具有Windows-1252的元标记。手动更改字符编码可以正确显示文档。
devstuff

5
您的句子“如果您要检测非UTF编码(即,没有BOM)”会引起误解;unicode标准不建议在utf-8文档中添加BOM。(并且此建议或缺乏建议是许多头痛的根源)。ref:en.wikipedia.org/wiki/Byte_order_mark#UTF-8
Tao

这样做是为了使您可以串联UTF-8字符串而不会累积冗余BOM。此外,与UTF-16不同,UTF-8不需要字节顺序标记。
sashoalm

26

您是否尝试过Mozilla通用字符集检测器的C#端口

来自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.");
        }
    }
}    

1
适用于Windows-1252类型。
seebiscuit 2014年

以及如何使用它来读取文本文件以使用字符串呢?CharsetDetector以字符串格式返回编码的名称,就是这样……
Bartosz 2014年

@Bartosz private Encoding GetEncodingFromString(string encoding) { try { return Encoding.GetEncoding(encoding); } catch { return Encoding.ASCII; } }
PrivatePyle

15

您无法检测到代码页

这显然是错误的。每个网络浏览器都有某种通用的字符集检测器来处理没有任何编码指示的页面。Firefox有一个。您可以下载代码,并查看其功能。在这里查看一些文档。基本上,这是一种启发式方法,但是效果很好。

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

这是我刚刚使用Google找到的另一个


39
“启发式”-浏览器并未完全检测到它,而是在进行有根据的猜测。“效果非常好”-那么那不是一直都在起作用吗?在我看来,我们同意了。
合资企业。

10
HTML标准规定,如果文档未定义字符集,则应将其视为编码为UTF-8。
乔恩·特劳特温

5
除非我们正在阅读非标准的HTML文档,否则这很酷。或非HTML文档。
科斯2012年

2
这个答案是错误的,所以我不得不投票。说您无法检测到代码页是错误的,这是错误的。您可以猜测,并且猜测可能相当不错,但是您不能“检测”代码页。
z80crew

1
@JonTrauntvein根据HTML5规范 a character encoding declaration is required even if the encoding is US-ASCII –缺少声明会导致使用启发式算法,而不是回到UTF8。
z80crew

9

我知道这个问题已经来不及了,并且该解决方案对某些问题不起作用(由于其英语为中心的偏见以及缺乏统计/经验测试),但是它对我来说非常有效,特别是对于处理上传的CSV数据:

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

优点:

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

注意:我是写这堂课的人,所以显然要带一点盐!:)



7

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

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

这个解决方案有点沉重。

我需要一些基本的编码检测(基于前四个字节)和xml charset检测-因此,我从Internet上获取了一些示例源代码,并添加了经过稍微修改的

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个字节,但是我正在加载整个文件。


7

如果有人正在寻找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();
        }
    }
}

非常好的解决方案。如果应允许两种以上的编码(UTF-8和ASCI 1252),则可以轻松地将ReadAsString()的主体包装在允许的编码循环中。
ViRuSTriNiTy

在尝试了大量示例之后,我终于了解了您的示例。我现在在一个快乐的地方。大声笑谢谢!!!!!!!
塞德里克

这可能不是如何检测1252 vs 1250的答案,但绝对应该是有或没有BOM的“如何检测UTF-8”的答案!
chuckc

4

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

给定该字典(哈希),您将输入文本并:

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

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

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


4

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

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

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


在Mac上通过自制软件:brew install uchardet
Paul B

3

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


这只是“编码” 链接这里..和描述说,我们必须提供编码..
SurajS

@SurajS:看看其他重载。
leppie 2015年

原始作者想检测文件的编码,该文件可能没有BOM标记。StreamReader根据签名从BOM表头检测编码。public StreamReader(Stream stream,bool detectEncodingFromByteOrderMarks)
ibondre

1

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

Enca会读取给定的文本文件,或者在没有输入文件的情况下读取标准输入,并使用有关其语言的知识(必须得到您的支持)以及解析,统计分析,猜测和黑魔法的组合来确定其编码。

这是GPL v2。


0

遇到了同样的问题,但尚未找到自动检测的好的解决方案。现在,我为此使用PsPad(www.pspad.com);)


0

由于它基本上可以归结为试探法,因此将来自同一来源的先前接收到的文件的编码用作第一提示可能会有所帮助。

大多数人(或应用程序)每次都在同一台机器上以几乎相同的顺序执行操作,因此很有可能当Bob创建.csv文件并将其发送给Mary时,它将始终使用Windows-1252或无论他的机器默认为什么。

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


0

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

所以我首先要做的是:StreamReader file = File.OpenText(fullfilename);

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

OpenText假定它为UTF-8。

您还可以像此新的StreamReader(fullfilename,true)一样创建StreamReader,第二个参数表示它应该尝试从文件的字节顺序标记中检测编码,但是在我的情况下不起作用。


@JohnMachin我同意这很罕见,但是它是在IMAP协议的某些部分中强制执行的。如果那是您的位置,则不必猜测。
三人

0

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


0

作为ITmeze帖子的附件,我使用了此功能来转换Mozilla通用字符集检测器的C#端口输出

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

MSDN



-1

读取文件时,我使用此代码检测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()

-1

自从被问到以来已经过去了10年(!),但我仍然没有提到MS的出色,非GPL解决方案:IMul​​tiLanguage2 API。

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

  1. 摆脱了GPL之类的许可问题,
  2. 可能永远支持和维护,
  3. 提供丰富的输出-编码/代码页的所有有效候选项以及置信度得分,
  4. 令人惊讶的易于使用(这是一个函数调用)。

这是一个本地COM调用,但这是Carsten Zeumer 的一些非常出色的工作,它处理.net使用的互操作混乱。周围还有其他一些,但是总的来说,这个库并没有得到应有的关注。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.