【问题标题】:Performant way to clear excessive old data in a text file清除文本文件中过多旧数据的高效方法
【发布时间】:2019-01-31 12:53:26
【问题描述】:

我有一些文本文件需要能够即时写入,而且经常写入。这些文件可能会变得非常大,但其中的文本在两次迭代之间可能会完全不同。

这意味着文本可以比以前更短。如果我不先清除旧数据,最终会在我的新数据末尾附加旧数据。如 MSDN 文档所示。

如果用较短的字符串(例如“Second run”)覆盖较长的字符串(例如“This is a test of the OpenWrite method”),文件将包含字符串的混合(“Second runtest of OpenWrite 方法”)。

但是文档没有指定补救甚至防止这种情况发生的方法。

目前我正在做以下事情:

File.WriteAllText(path, string.Empty);
using (Stream file = File.OpenWrite(path))
{
    file.Write(dataToWrite, 0, dataToWrite.Length);
}

我使用File.WriteAllText(path, string.Empty) 清空现有文件的内容,然后将新内容写入文件。

但是,必须检查整个文件两次感觉很浪费(首先清除它,然后将新数据写入其中)。

有没有一种方法可以用我的新数据覆盖旧数据,只检查“剩余”数据并清除它,而不用两次检查整个文件?

不一定非要使用Stream.Write。任何可以完成工作且速度更快的替代方案都是可以接受的。


结果

在不同的机器上运行 5 次写入 2441 kb 数据(并清除所有旧数据)的 100.000 次迭代后,得出以下结果:

  • 我在上面找到的原始方法平均采用4.75589 ms
  • Anderson Pimentel 的 answer 使用 WriteAllBytes 平均得到 4.28946 ms
  • 最快 Dark Falcon 的answer 使用file.Write 并截断平均使用4.14433 ms(并且是最快/最一致的)。
  • File.Delete 旧文件和使用FileStream.Write 创建新文件平均占用5.31883 ms
  • MeJustAndrew 的 answer 执行上述操作,但多线程平均占用了 8.12726 ms。 (虽然我不得不承认这很可能是因为我的执行不力,但我在多线程方面的经验并不丰富)

请注意,这些结果适用于 my implementation 和硬件。结果可能因不同的硬件而异。

【问题讨论】:

  • 您是否尝试过将性能与 File.Delete 进行比较并重新创建一个新文件来写入所有文本?这是个有趣的问题。我会想象在编写所有文本时可以选择覆盖整个文件:)
  • File.WriteAllBytes 应该在一行中做你想做的事情。
  • @Joe 谢谢!我不知道WriteAllBytes 也是一个东西。它看起来更干净并且它更快。运行一个小型测试用例,下载一个 6.5mb 的文本文件,它实际上已经快了 2 毫秒(105 毫秒对 103 毫秒)。 (我看到安德森刚刚编辑了他的答案以使用 Bytes aswel)
  • @remy_rm 你可以接受 Anderson Pimentel 的回答。
  • @Joe 虽然它在小规模上看起来更快,但在处理更大的文件时实际上看起来性能较差。我在原始问题中包含了一个测试用例。

标签: c# performance file


【解决方案1】:

我建议截断文件:

using (FileStream file = File.OpenWrite(path))
{
    file.Write(dataToWrite, 0, dataToWrite.Length);
    file.SetLength(dataToWrite.Length);
}

您应该测试这是否比写入新文件、删除旧文件以及将新文件重命名为旧名称更好。

【讨论】:

  • 接受这个作为答案,因为它似乎是写入文件和擦除旧内容的最快(也是最一致的!)方法
【解决方案2】:

你可以写:

File.WriteAllBytes(path, dataToWrite);

根据MSDN

创建一个新文件,将指定的字节数组写入文件,然后 然后关闭文件。如果目标文件已经存在,它是 覆盖。

【讨论】:

  • 您可以使用WriteAllBytes 方法。我更新了答案。
  • 虽然我认为它看起来肯定更优雅。这似乎实际上更慢。运行基本测试 5 次后(将大约 185MB 的数据复制 10 次,到 192MB 的文件中)它慢了大约 9%,尽管时间更加一致。 (测试+结果pastebin.com/p3DaqVBB
  • @remy_rm 不需要测试。 implemented 和你的几乎一样。
【解决方案3】:

正如我所提到的,多线程方法如下所示:

class FileWriter
{
    private int index;
    private string fileName = "file.txt";
    private readonly object obj = new object();
    private string FileName { get { lock (obj) { return fileName + index; } } }

    public void Write(string content)
    {
        lock (obj)
        {
            int deleteIndex = index;
            new Thread(() => DeleteOldFile(deleteIndex)).Start();
            index++;
            new Thread(() => File.WriteAllText(fileName + index, content)).Start();
        }
    }

    public string GetFileContent()
    {
        lock (obj)
        {
            return File.ReadAllText(FileName);
        }
    }

    private void DeleteOldFile(int fileNumber)
    {
        var fileToBeDeleted = fileName + fileNumber;
        if (File.Exists(fileToBeDeleted))
            File.Delete(fileToBeDeleted);
    }
}

注意:我不保证此代码的正确行为,因为我没有对其进行测试。

【讨论】:

  • 这种方法确实适用于我需要同时处理多个文件的情况!但是,当单个文件被编辑几次时,它会失去从多线程中获得的收益,因为它在删除完成之前无法写入。
  • @remy_rm 问题是您在删除发生的同时开始写入,因此它可能比单次覆盖文件更快..
  • 嗯,因为它会引发错误The process cannot access the file <file> because it is being used by another process。我想我可以对其进行编辑,以便删除旧文件,同时使用临时名称创建新文件,然后将其重命名为旧文件名。 (我现在看到你的例子增加了索引来实现这一点,我只需要添加一些恢复到原始文件名的东西)。虽然我认为如果写入/重命名发生在删除完成之前,这会产生竞争条件? (对多线程相当陌生,所以如果我错了,请纠正我)
  • 是的,它现在可以工作了!现在要对更大的样本量进行一些测试。感谢您的帮助!
  • @remy_rm 我很高兴听到它有效!让我知道你的测试结果如何。
猜你喜欢
  • 1970-01-01
  • 2021-11-03
  • 2014-02-13
  • 2013-06-06
  • 1970-01-01
  • 2014-07-07
  • 1970-01-01
  • 2015-04-03
  • 1970-01-01
相关资源
最近更新 更多