如何在c#中确定文件是二进制还是文本?

How can I determine if a file is binary or text in c#?

本问题已经有最佳答案,请猛点这里访问。

我需要用80%来确定一个文件是二进制的还是文本的,有没有任何方法可以在C中快速、肮脏/丑陋地完成它?


有一种方法叫做马尔可夫链。扫描两种类型的几个模型文件,并针对从0到255的每个字节值收集后续值的统计信息(基本上是概率)。这将为您提供一个64kb(256x256)的配置文件,您可以将运行时文件与之进行比较(在%阈值内)。

假设浏览器的自动检测编码功能就是这样工作的。


我可能会寻找大量的控制字符,这些字符通常出现在二进制文件中,但很少出现在文本文件中。二进制文件往往使用0,因此只测试多个0字节就足以捕获大多数文件。如果您关心本地化,那么也需要测试多字节模式。

不过,如前所述,您可能总是不走运,并得到一个看起来像文本的二进制文件,反之亦然。


分享我的解决方案,希望它可以帮助其他人,因为它可以帮助我从这些帖子和论坛。

背景

我一直在研究和探索同样的解决方案。但是,我希望它是简单的或者稍微扭曲的。

然而,大多数尝试在这里以及其他来源提供复杂的解决方案,并深入到Unicode、UTF系列、BOM、编码、字节顺序中。在这个过程中,我也开始越野,进入ASCII表和代码页。

无论如何,我已经提出了一个基于流阅读器和自定义控制字符检查的解决方案。

它的构建考虑了论坛和其他地方提供的各种提示和技巧,例如:

  • 检查许多控制字符,例如查找多个连续的空字符。
  • 检查UTF、Unicode、编码、BOM、字节顺序和类似方面。
  • 我的目标是:

  • 它不应该依赖字节顺序、编码和其他更复杂的深奥工作。
  • 它应该相对容易实现和理解。
  • 它应该适用于所有类型的文件。
  • 提供的解决方案适用于测试数据,包括MP3、EML、TXT、INFO、FLV、MP4、PDF、GIF、PNG、JPG。到目前为止,它给出了预期的结果。

    解决方案的工作原理

    我依赖streamreader默认构造函数来做它在确定默认使用utf8编码的与文件编码相关的特性方面能做的最好的事情。

    我创建了自己版本的检查自定义控件char条件,因为char.iscontrol似乎不有用。它说:

    Control characters are formatting and other non-printing characters,
    such as ACK, BEL, CR, FF, LF, and VT. Unicode standard assigns code
    points from \U0000 to \U001F, \U007F, and from \U0080 to \U009F to
    control characters. These values are to be interpreted as control
    characters unless their use is otherwise defined by an application. It
    considers LF and CR as control characters among other things

    因为文本文件至少包括CR和LF,所以它没有用处。

    解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    static void testBinaryFile(string folderPath)
    {
        List<string> output = new List<string>();
        foreach (string filePath in getFiles(folderPath, true))
        {
            output.Add(isBinary(filePath).ToString() +"  ---- " + filePath);
        }
        Clipboard.SetText(string.Join("
    "
    , output), TextDataFormat.Text);
    }

    public static List<string> getFiles(string path, bool recursive = false)
    {
        return Directory.Exists(path) ?
            Directory.GetFiles(path,"*.*",
            recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).ToList() :
            new List<string>();
    }    

    public static bool isBinary(string path)
    {
        long length = getSize(path);
        if (length == 0) return false;

        using (StreamReader stream = new StreamReader(path))
        {
            int ch;
            while ((ch = stream.Read()) != -1)
            {
                if (isControlChar(ch))
                {
                    return true;
                }
            }
        }
        return false;
    }

    public static bool isControlChar(int ch)
    {
        return (ch > Chars.NUL && ch < Chars.BS)
            || (ch > Chars.CR && ch < Chars.SUB);
    }

    public static class Chars
    {
        public static char NUL = (char)0; // Null char
        public static char BS = (char)8; // Back Space
        public static char CR = (char)13; // Carriage Return
        public static char SUB = (char)26; // Substitute
    }

    如果您尝试上述解决方案,请告诉我它是否适用于您。

    其他有趣的相关链接:

    • 关于unicode.org上的utf和bom
    • Unicode示例文件
    • 如何检测文本文件的编码和
    • 在CSharp中检测文件编码


    如果真正的问题是"是否可以使用streamreader/streamwriter在不进行修改的情况下读取和写入此文件?",那么答案是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    /// <summary>
    /// Detect if a file is text and detect the encoding.
    /// </summary>
    /// <param name="encoding">
    /// The detected encoding.
    /// </param>
    /// <param name="fileName">
    /// The file name.
    /// </param>
    /// <param name="windowSize">
    /// The number of characters to use for testing.
    /// </param>
    /// <returns>
    /// true if the file is text.
    /// </returns>
    public static bool IsText(out Encoding encoding, string fileName, int windowSize)
    {
        using (var fileStream = File.OpenRead(fileName))
        {
        var rawData = new byte[windowSize];
        var text = new char[windowSize];
        var isText = true;

        // Read raw bytes
        var rawLength = fileStream.Read(rawData, 0, rawData.Length);
        fileStream.Seek(0, SeekOrigin.Begin);

        // Detect encoding correctly (from Rick Strahl's blog)
        // http://www.west-wind.com/weblog/posts/2007/Nov/28/Detecting-Text-Encoding-for-StreamReader
        if (rawData[0] == 0xef && rawData[1] == 0xbb && rawData[2] == 0xbf)
        {
            encoding = Encoding.UTF8;
        }
        else if (rawData[0] == 0xfe && rawData[1] == 0xff)
        {
            encoding = Encoding.Unicode;
        }
        else if (rawData[0] == 0 && rawData[1] == 0 && rawData[2] == 0xfe && rawData[3] == 0xff)
        {
            encoding = Encoding.UTF32;
        }
        else if (rawData[0] == 0x2b && rawData[1] == 0x2f && rawData[2] == 0x76)
        {
            encoding = Encoding.UTF7;
        }
        else
        {
            encoding = Encoding.Default;
        }

        // Read text and detect the encoding
        using (var streamReader = new StreamReader(fileStream))
        {
            streamReader.Read(text, 0, text.Length);
        }

        using (var memoryStream = new MemoryStream())
        {
            using (var streamWriter = new StreamWriter(memoryStream, encoding))
            {
            // Write the text to a buffer
            streamWriter.Write(text);
            streamWriter.Flush();

            // Get the buffer from the memory stream for comparision
            var memoryBuffer = memoryStream.GetBuffer();

            // Compare only bytes read
            for (var i = 0; i < rawLength && isText; i++)
            {
                isText = rawData[i] == memoryBuffer[i];
            }
            }
        }

        return isText;
        }
    }


    虽然这不是万无一失的,但应该检查它是否有任何二进制内容。

    1
    2
    3
    4
    5
    6
    public bool HasBinaryContent(string content)
    {
        return content.Any(ch => char.IsControl(ch) && ch != '
    '
    && ch != '
    '
    );
    }

    因为如果存在任何控制字符(除了标准的

    ),那么它可能不是文本文件。


    好问题!我很惊讶.NET并没有为此提供一个简单的解决方案。

    下面的代码可以帮助我区分图像(PNG、JPG等)和文本文件。

    根据罗恩·沃霍尔和亚当·布鲁斯的建议,我刚刚检查了前512字节的连续空值(0x00

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (File.Exists(path))
    {
        // Is it binary? Check for consecutive nulls..
        byte[] content = File.ReadAllBytes(path);
        for (int i = 1; i < 512 && i < content.Length; i++) {
            if (content[i] == 0x00 && content[i-1] == 0x00) {
                return Convert.ToBase64String(content);
            }
        }
        // No? return text
        return File.ReadAllText(path);
    }

    显然,这是一种快速而肮脏的方法,但是可以通过将文件分成10个512字节的块,并检查其中8个连续的空值(就个人而言,如果其中2个或3个匹配,我会推断出它的二进制文件-空值在文本文件中很少见)。

    这应该为你所追求的提供一个很好的解决方案。


    快速而脏的是使用文件扩展名并查找常见的文本扩展名,如.txt。为此,可以使用path.getextension调用。任何其他东西都不会真正被归类为"快速",尽管它很可能是脏的。


    一个真正肮脏的方法是构建一个只使用标准文本、标点符号、符号和空白字符的regex,在文本流中加载文件的一部分,然后对regex运行它。根据问题域中哪些文件是纯文本文件,没有成功的匹配将指示二进制文件。

    要解释Unicode,请确保在流中标记编码。

    这真是次优,但你说得又快又脏。


    另一种方法是使用ude检测文件的字符集。如果charset检测成功,您可以确定它是文本,否则它是二进制的。因为binary没有字符集。

    当然,您可以使用除ude之外的其他字符集检测库。如果字符集检测库足够好,这种方法可以达到100%的正确性。


    另一种方法是:确定二进制数组的长度,表示文件的内容,并将其与将给定的二进制数组转换为文本后的字符串长度进行比较。

    如果长度相同,文件中就没有"不可读"的符号,而是文本(我敢肯定是80%)。


    http://codesnipers.com/?q=node/68描述了如何使用字节顺序标记(可能出现在文件中)检测UTF-16和UTF-8。它还建议循环遍历一些字节,看看它们是否符合UTF-8多字节序列模式(如下),以确定您的文件是否是文本文件。

    • 0xXXXXXX ASCII<0x80(128)
    • 110xxxxx 10xxxx 2字节>=0x80
    • 1110XXXX 10XXXX 10XXXX 3字节>。=0x400
    • 11110xxx 10xxxx 10xxxx10XXXXXX 4字节>=0x1000