鉴于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);
}
为什么写入磁盘比在内存中附加字符串快得多?我知道添加一个字符串需要将整个字符串复制到一个新的内存位置,但是附加一个字符串比写入磁盘慢几个数量级,这似乎是反直觉的。
答案 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
更快。