缩短读取二进制文件的执行时间 - 使用BinaryReader非常慢

本文关键字:使用 BinaryReader 非常 执行时间 读取 二进制文件 | 更新日期: 2023-09-27 18:34:28

我编写了代码来处理一个大的二进制文件(超过 2 GB(,每个文件以 1024 字节的块读取。该文件包含数据块,每个块由两个字节按顺序分隔,5D5B = 0x5D 0x5B。

代码有效,但对于大文件,执行时间超过 1:30 小时,当我使用一种等效的 Ruby 脚本执行相同操作时,执行时间不到 15 分钟。

您可以使用下面的文件"input.txt"测试代码,您将看到它正确打印了每个块。您可以创建.txt带有"File.WriteAllBytes((..."或在记事本中创建文件"input.txt",其中包含以下内容,不带(双引号(:

"][如何][许多][单词][

我们][有][这里?[6][或][更多?

在此示例中,我正在使用 BinaryReader 类和 seek 方法来读取 20 字节的块(大文件 1024 字节(,因为文件仅包含 50 个字节,然后在每个块中查找最后一个块的开头的位置并将其存储在 var lastPos 中,因为最后一个块可能不完整。

有没有办法改进我的代码以获得更快的执行时间?

我不确定问题是 BinaryReader 还是与数千个搜索操作有关。第一个目标是让每个块对每个块应用一些解析,但似乎大部分时间都消耗在块的分离上。

static void Main(string[] args)
{
    File.WriteAllBytes("C:/input.txt", new byte[] { 0x5d, 0x5b, 0x48, 0x6f, 0x77, 0x5d, 0x5b, 0x6d, 0x61, 0x6e,
                                                    0x79, 0x5d, 0x5b, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x5d, 0x5b,
                                                    0x77, 0x65, 0x5d, 0x5b, 0x68, 0x61, 0x76, 0x65, 0x5d, 0x5b,
                                                    0x68, 0x65, 0x72, 0x65, 0x3f, 0x5d, 0x5b, 0x36, 0x5d, 0x5b,
                                                    0x6f, 0x72, 0x5d, 0x5b, 0x6d, 0x6f, 0x72, 0x65, 0x3f, 0x5d } );
    using (BinaryReader br = new BinaryReader(File.Open("C:/input.txt", FileMode.Open)))
    {
        int lastPos = 0;
        int EachChunk = 20;
        long ReadFrom = 0;
        int c = 0;
        int count = 0;
        while(lastPos != -1 ) {
            lastPos = -1;
            br.BaseStream.Seek(ReadFrom, SeekOrigin.Begin);
            byte[] data = br.ReadBytes(EachChunk);
            //Loop to look for position of last clock in current chunk
            int k = data.Length - 1;
            while(k > 0 && lastPos == -1) {
                lastPos = (data[k] == 91 && data[k-1] == 93 ? (k - 1) : (-1) );
                k--;
            }
            if (lastPos != -1) {
                Array.Resize(ref data, lastPos);
            } // Resizing array up to the last block position
            // Storing position of pointer where will begin next chunk
            ReadFrom += lastPos + 2;
            //Converting Binary data to string of hex numbers.
            SoapHexBinary shb = new SoapHexBinary(data);
            //Replace separator by Newline
            string str = shb.ToString().Replace("5D5B", Environment.NewLine);
            //Use StringReader to process each block as a line, using the newline as separator
            using (StringReader reader = new StringReader(str))
            {
                // Loop over the lines(blocks) in the string.
                string Block;
                count = c;
                while ((Block = reader.ReadLine()) != null)
                {
                    if ((String.IsNullOrWhiteSpace(Block) ||
                         String.IsNullOrEmpty(Block)) == false) {
                        // +++++ Further process for each block +++++++++++++++++++++++++
                        count++;
                        Console.WriteLine("Block # {0}: {1}", count, Block);
                        // ++++++++++++++++++++++++++++++++++++++++++++++++++
                    }
                }
            }
            c = count;
        }
    }
    Console.ReadLine();
}

更新:

我发现了一个问题。在Mike Burdick的代码中,缓冲区在找到5B时开始增长,并在找到5D时打印,但由于每个块由0x5D0x5B分隔,如果任何块中单独存在5D或单独的5B,则代码开始加载或清除缓冲区,并且仅在找到序列5D5B时才应加载缓冲区, 不仅当找到5B时,如果不是,结果是不同的。

您可以使用此输入进行测试,我在块中添加了 5D 或 5B。我仅在找到 5D5B 并且可以加载缓冲区时才恢复,因为 5D5B 就像"换行符"分隔符。

    File.WriteAllBytes("C:/input1.txt", new byte[] {
                                        0x5D, 0x5B, 0x48, 0x5D, 0x77, 0x5D, 0x5B, 0x6d, 0x5B, 0x6e,
                                        0x5D, 0x5D, 0x5B, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x5D, 0x5B,
                                        0x77, 0x65, 0x5D, 0x5B, 0x68, 0x61, 0x76, 0x65, 0x5D, 0x5B,
                                        0x68, 0x65, 0x72, 0x65, 0x3f, 0x5D, 0x5B, 0x36, 0x5D, 0x5B,
                                        0x6f, 0x72, 0x5D, 0x5B, 0x6d, 0x6f, 0x72, 0x65, 0x3f, 0x5D });

更新 2:

我尝试过迈克·伯迪克的代码,但它没有给出正确的输出。例如,如果更改输入文件的内容以包含以下内容:

82-F][如何]]][ma[ny]][words%][我们][[有][这里?

输出

应该是(下面的输出以 ASCII 显示,以便更清楚地看到它(:

    82-F
    How]]
    ma[ny]
    words%
    we
    [have
    here?]]

除此之外,你认为BinaryReader是一种缓慢的吗?当我使用更大的文件进行测试时,执行速度仍然很慢。

更新#3:

我一直在测试Mike Burdick的代码。也许这不是对 Mike Burdick 代码的最佳修改,因为我已经修改了处理可能出现在每个块中间的][。它似乎有效,并且只有在文件以"]"结尾时才无法打印最后一个"]"。

例如,与以前相同的内容:"][How][many][words][we][have][here?][6][or][more?]"

我对Mike Burdick代码的修改是:

    static void OptimizedScan(string fileName)
    {
        const byte startDelimiter = 0x5d;
        const byte endDelimiter = 0x5b;
        using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open)))
        {
            List<byte> buffer = new List<byte>();
            List<string> buffer1 = new List<string>();
            bool captureBytes = false;
            bool foundStartDelimiter = false;
            int wordCount = 0;
            SoapHexBinary hex = new SoapHexBinary();
            while (true)
            {
                byte[] chunk = reader.ReadBytes(1024);
                if (chunk.Length > 0)
                {
                    foreach (byte data in chunk)
                    {
                        if (data == startDelimiter && foundStartDelimiter == false)
                        {
                            foundStartDelimiter = true;
                        }
                        else if (data == endDelimiter && foundStartDelimiter)
                        {
                            wordCount = DisplayWord(buffer, wordCount, hex);
                            // Start capturing
                            captureBytes = true;
                            foundStartDelimiter = false;
                        }
                        else if ((data == startDelimiter && foundStartDelimiter) ||
                                 (data == endDelimiter && foundStartDelimiter == false))
                        {
                            buffer.Add(data);
                        }
                        else if (captureBytes)
                        {
                            buffer.Add(data);
                        }
                    }
                }
                else
                {
                    break;
                }
            }
            if (foundStartDelimiter)
            {
                buffer.Add(startDelimiter);
            }
            DisplayWord(buffer, wordCount, hex);

缩短读取二进制文件的执行时间 - 使用BinaryReader非常慢

我认为这在代码方面更快、更简单:

    static void OptimizedScan(string fileName)
    {
        const byte startDelimiter = 0x5d;
        const byte endDelimiter = 0x5b;
        using (BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open)))
        {
            List<byte> buffer = new List<byte>();
            bool captureBytes = false;
            bool foundStartDelimiter = false;
            int wordCount = 0;
            SoapHexBinary hex = new SoapHexBinary();
            while (true)
            {
                byte[] chunk = reader.ReadBytes(1024);
                if (chunk.Length > 0)
                {
                    foreach (byte data in chunk)
                    {
                        if (data == startDelimiter)
                        {
                            foundStartDelimiter = true;
                        }
                        else if (data == endDelimiter && foundStartDelimiter)
                        {
                            wordCount = DisplayWord(buffer, wordCount, hex);
                            // Start capturing
                            captureBytes = true;
                            foundStartDelimiter = false;
                        }
                        else if (captureBytes)
                        {
                            if (foundStartDelimiter)
                            {
                                buffer.Add(startDelimiter);
                            }
                            buffer.Add(data);
                        }
                    }
                }
                else
                {
                    break;
                }
            }
            if (foundStartDelimiter)
            {
                buffer.Add(startDelimiter);
            }
            DisplayWord(buffer, wordCount, hex);
        }
    }