【问题标题】:How to rewrite file as byte array fast C#如何快速将文件重写为字节数组C#
【发布时间】:2018-05-24 18:21:12
【问题描述】:

您好,我正在尝试通过替换字节来重写文件,但是重写大文件需要太多时间。例如,在 700MB 上,这段代码工作了大约 6 分钟。请帮我让它在不到 1 分钟的时间内工作。

static private void _12_56(string fileName)
{
    byte[] byteArray = File.ReadAllBytes(fileName);
    for (int i = 0; i < byteArray.Count() - 6; i += 6)
    {
        Swap(ref byteArray[i], ref byteArray[i + 4]);
        Swap(ref byteArray[i + 1], ref byteArray[i + 5]);
    }
    File.WriteAllBytes(fileName, byteArray);
}

【问题讨论】:

  • 这可能很慢,因为您正在将整个文件读入内存。我不知道Swap 做了什么,是否有必要保存整个文件,或者您可以只读取 1MB 的块并一次处理吗?使用 Visual Studio 分析器来准确查看慢的地方也是一个好主意。
  • 你可以查看这个问题/答案,很好的回复! stackoverflow.com/questions/955911/…
  • @JimW Swap 只使用临时变量交换字节。对我来说,每 6 个字节用第 4 个字节替换第 1 个字节,用第 5 个替换第 2 个字节是很重要的。
  • 您可以按字节读取和写入,但不确定是否会更快。

标签: c# arrays system.io.file


【解决方案1】:

以可被 6 整除的字节块读取文件。 替换每个块中必要的字节,并在读取下一个块之前将每个块写入另一个文件。

您也可以尝试在写入下一个块的同时执行下一个块的读取:

using( var source = new FileStream(@"c:\temp\test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    using( var target = new FileStream(@"c:\temp\test.txt", FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
    {
        await RewriteFile(source, target);
    }
}


private async Task RewriteFile( FileStream source, FileStream target )
{
    // We're reading bufferSize bytes from the source-stream inside one half of the buffer
    // while the writeTask is writing the other half of the buffer to the target-stream.

    // define how many chunks of 6 bytes you want to read per read operation
    int chunksPerBuffer = 1;
    int bufferSize = 6 * chunksPerBuffer;

    // declare a byte array that contains both the bytes that are read
    // and the bytes that are being written in parallel.
    byte[] buffer = new byte[bufferSize * 2];
    // curoff is the start-position of the bytes we're working with in the 
    // buffer
    int curoff = 0;

    Task writeTask = Task.CompletedTask;
    int len;

    // Read the desired number of bytes from the file into the buffer.
    // In the first read operation, the bytes will be placed in the first
    // half of the buffer.  The next read operation will read them in 
    // the second half of the buffer.      
    while ((len = await source.ReadAsync(buffer, curoff, bufferSize).ConfigureAwait(false)) != 0)
    {
        // Swap the bytes in the current buffer.
        // When reading x * 6 bytes in one go, every 1st byte will be replaced by the 4th byte; every 2nd byte will be replaced by the 5th byte.
        for (int i = curoff; i < bufferSize + curoff; i += 6)
        {
            Swap(ref buffer[i], ref buffer[i + 4]);
            Swap(ref buffer[i + 1], ref buffer[i + 5]);
        }

        // wait until the previous write-task completed.
        await writeTask.ConfigureAwait(false);
        // Start writing the bytes that have just been processed.
        // Do not await the task here, so that the next bytes 
        // can be read in parallel.
        writeTask = target.WriteAsync(buffer, curoff, len);

        // Position the pointer to the beginnen of the other part
        // in the buffer
        curoff ^= bufferSize;                        

    }

    // Make sure that the last write also finishes before closing
    // the target stream.
    await writeTask.ConfigureAwait(false);
}

上面的代码应该并行读取一个文件、交换字节并重写到同一个文件。

【讨论】:

  • 作为附录,我会考虑不担心具体分为 6。选择合适的静态块大小并根据需要异步读取尽可能多的块。这样,如果您的文件大小或性能要求发生变化,您就不会被“锁定”。
  • 很确定引用任务并像这样多次运行它是行不通的。每次迭代都需要对 readAsync 进行新调用。
  • 不,它没有。它总是从文件的开头读取相同的bufferSize 字节,并且永远不会终止,如果文件大于缓冲区。
  • 出于安全/容量/交易原因,“到另一个文件”可能是不可接受的。
  • @FrederikGheysels 对不起 - 我读了你的代码三遍,错过了你重新分配 readTask。为什么不在循环中调用 ReadAsync 呢?真的把我扔了……
【解决方案2】:

正如另一个答案所说,您必须分块读取文件。

由于您正在重写同一个文件,因此使用同一个流进行读取和写入是最简单的。

using(var file = File.Open(path, FileMode.Open, FileAccess.ReadWrite)) {        
    // Read buffer. Size must be divisible by 6
    var buffer = new byte[6*1000]; 

    // Keep track of how much we've read in each iteration
    var bytesRead = 0;      

    // Fill the buffer. Put the number of bytes into 'bytesRead'.
    // Stop looping if we read less than 6 bytes.
    // EOF will be signalled by Read returning -1.
    while ((bytesRead = file.Read(buffer, 0, buffer.Length)) >= 6)
    {   
        // Swap the bytes in the current buffer
        for (int i = 0; i < bytesRead; i += 6)
        {
            Swap(ref buffer[i], ref buffer[i + 4]);
            Swap(ref buffer[i + 1], ref buffer[i + 5]);
        }

        // Step back in the file, to where we filled the buffer from
        file.Position -= bytesRead;
        // Overwrite with the swapped bytes
        file.Write(buffer, 0, bytesRead);
    }
}

【讨论】:

  • 我更喜欢这个答案,但您也可以打开 2 个 FileStreams (1 r, 1 w) 到同一个文件。您的方法可能会浪费一些较低级别的缓冲。
  • @gnud 只是想知道,为什么分块文件更有效?
  • 谢谢,它在 700 MB 文件上运行大约 30 秒。
  • @johnny5 我想到这个的方式来自于旋转磁盘的那一天。使用旋转磁盘,如果您执行许多小型读/写操作,您可能会发出过多的寻道(将读/写头移动到磁盘上)。物理原因与 SSD 不同。尽管如此,如果没有缓冲,每个read/write 都会有一个系统调用。我确信在操作系统级别和磁盘级别会发生“不可见”的缓冲,并且可能不会有重大差异。但很难测试 - 正是因为这些缓存。
  • @HenkHolterman 测试起来会很有趣。做起来也很简单。只需添加另一个流,从一个读取,写入另一个,不要更改Position。同样,由于磁盘缓存,很难测试这些东西。使用热缓存很容易测试 - 而不是冷缓存。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-23
  • 1970-01-01
  • 2017-05-21
  • 1970-01-01
  • 2015-08-25
  • 2016-08-17
相关资源
最近更新 更多