为什么写入文件比附加字符串更快?

时间:2016-11-30 15:25:21

标签: c# string streamwriter

鉴于RAM比硬盘快得多,我对以下代码感到惊讶。

我试图根据一列的值拆分CSV文件,并将该单元格中不同值的每一行写入不同的文件。

我在尝试:

List<string> protocolTypes = new List<string>();
List<string> splitByProtocol = new List<string>();
foreach (string s in lineSplit)
{
    string protocol = getProtocol();
    index = protocolTypes.IndexOf(protocol);
    splitByProtocol[index] = splitByProtocol[index] + s + "\n";
}

这需要很长时间,但将其更改为流编写器要快得多:

List<string> protocolTypes = new List<string>();
List<StreamWriter> splitByProtocol = new List<StreamWriter>();
foreach (string s in lineSplit)
{
    string protocol = getProtocol();
    index = protocolTypes.IndexOf(protocol);
    splitByProtocol[index].WriteLine(s);
}

为什么写入磁盘比在内存中附加字符串快得多?我知道添加一个字符串需要将整个字符串复制到一个新的内存位置,但是附加一个字符串比写入磁盘慢几个数量级,这似乎是反直觉的。

3 个答案:

答案 0 :(得分:3)

如果字符串变得很大(很多MB),那么复制它们肯定会很费时间。

然而,最大的打击可能是由许多不再需要的旧字符串引起的,它们作为垃圾堆在堆上,等待收集。所以垃圾收集器会启动,甚至可能多次,每次都会暂停你的程序。

对于像这样的循环构造的字符串,请始终考虑使用StringBuilder。要匹配您的示例代码:

List<StringBuilder> splitByProtocol = new List<StringBuilder>();
foreach (string s in lineSplit)
{
    string protocol = getProtocol();
    index = protocolTypes.IndexOf(protocol);
    splitByProtocol[index].AppendLine(s);
}

答案 1 :(得分:2)

首先,它为新字符串分配(大量)内存。然后它复制现有的字符串,以及附加的部分,字节为byte。这需要相当多的周期,并且对于每个循环,字符串变得更长,因此整个操作时间与循环数呈指数关系。

Gen1的垃圾收集也意味着最新的字符串被复制到Gen2(因此再次复制)。这将填满一堆这些旧字符串等,所以我们得到Gen2。这种方法在GC上产生了相当多的开销。

对于磁盘它只写入流,所以它首先在内存中(快速)然后是磁盘缓存(快速),直到它最终写入磁盘(缓慢,但该部分被缓冲,因此它&# 39;看起来很快)。 此外,它只进行一次,因此性能与循环次数非常相似。

BTW你可能想看看StringBuilder,这可能会更快。

答案 2 :(得分:2)

首先确保您的测量结果正常。

如果仍然,StreamWriter使用缓冲区写入,则追加一个字符串,每次都会创建一个字符串,最后会有过多的内存分配,而流编写器仍在缓存。请注意,您没有刷新,这意味着在刷新之前不会写入文件(这不是您的代码所强制的),因此可能意味着您只是存储到比字符串附加更高效的内存存储。即使它被冲洗,它也会立刻完成。使用快速磁盘,您最终会比过于昂贵的字符串连接更快。

如果您使用StringBuilder作为第一个代码,您将看到执行时间会显着下降。然后你会看到性能的真正差异,我相信你会看到StringBuilder更快。