C# 为什么写入文件比追加字符串快?
考虑到RAM比硬盘快得多,我对下面的代码感到惊讶 我试图根据一列的值拆分CSV文件,并将该单元格中具有不同值的每一行写入不同的文件 我试着:C# 为什么写入文件比追加字符串快?,c#,string,streamwriter,C#,String,Streamwriter,考虑到RAM比硬盘快得多,我对下面的代码感到惊讶 我试图根据一列的值拆分CSV文件,并将该单元格中具有不同值的每一行写入不同的文件 我试着: List<string> protocolTypes = new List<string>(); List<string> splitByProtocol = new List<string>(); foreach (string s in lineSplit) { string protocol =
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 protocolTypes=new List();
List splitByProtocol=新列表();
foreach(lineSplit中的字符串s)
{
string protocol=getProtocol();
index=protocolTypes.IndexOf(协议);
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);
}
List protocolTypes=new List();
List splitByProtocol=新列表();
foreach(lineSplit中的字符串s)
{
string protocol=getProtocol();
index=protocolTypes.IndexOf(协议);
splitByProtocol[索引]。写入线;
}
为什么写入磁盘比在内存中追加字符串快得多?我知道添加字符串需要将整个字符串复制到一个新的内存位置,但添加字符串比写入磁盘慢几个数量级,这似乎违反直觉。首先它为新字符串分配(大量)内存。然后它逐字节复制现有字符串和附加的部分。这需要相当多的周期,对于每个循环,字符串都会变长,因此总体操作时间与循环数成指数关系
此外,Gen1的垃圾收集意味着将最新的字符串复制到Gen2(再次复制)。这将填充一堆这些旧字符串等,所以我们进入Gen2。这种方法会在GC上产生相当大的开销
对于磁盘,它只是写入流,所以它首先在内存中(快),然后在磁盘缓存中(快),直到它最终写入磁盘(慢,但该部分被缓冲,所以看起来非常快)。
而且它只执行一次,所以性能与循环的数量几乎成线性关系
顺便说一句,您可能希望查看StringBuilder,这可能会更快 首先确保您的测量结果正常 如果仍然,
StreamWriter
使用一个缓冲区进行写入,您将附加一个字符串,该字符串将再次创建一个字符串,最终将有过多的内存分配,而StreamWriter仍在缓存。请注意,您没有刷新,这意味着文件在刷新之前不会被写入(这不是您的代码强制执行的),因此可能意味着您只是将文件存储到一个比字符串附加更高效的内存存储器中。即使它被冲了,它也会立刻冲。使用快速磁盘,最终的速度比过于昂贵的字符串连接要快
如果将
StringBuilder
用于第一个代码,您将看到执行时间将显著减少。然后您将看到性能上的真正差异,我相信您将看到StringBuilder
更快。如果字符串变得巨大(很多MB),那么复制它们肯定会很耗时
然而,最大的打击可能是由于许多不再需要的旧字符串,像垃圾一样放在堆上,等待收集。因此,垃圾收集器将启动,甚至可能多次,每次都暂停您的程序
在这样的循环中构建的字符串,总是考虑使用<代码> StringBuilder < /代码>。要匹配示例代码,请执行以下操作:
List<StringBuilder> splitByProtocol = new List<StringBuilder>();
foreach (string s in lineSplit)
{
string protocol = getProtocol();
index = protocolTypes.IndexOf(protocol);
splitByProtocol[index].AppendLine(s);
}
List splitByProtocol=new List();
foreach(lineSplit中的字符串s)
{
string protocol=getProtocol();
index=protocolTypes.IndexOf(协议);
splitByProtocol[索引]。追加行;
}
它似乎不是真正的代码,因为您没有将项目添加到列表中。每个+
操作都会创建一个新字符串(需要分配内存)。请改用StringBuilder
。@MikeS159:除了可能的IndexOutOfBoundException
之外,这个简单的字符串连接不会导致任何明显的性能问题。你到底是怎么测量的?我猜大部分工作都是在getProtocol
@TimSchmelter中完成的,如果您认为简单的字符串连接不会导致性能问题,那么您在字符串操作方面似乎做得不多。这在时间和空间上都是一个O(n^2)操作。缓冲区填充时会自动刷新,因此他不会再写入文件。同意,澄清@ServyI忘记了StringBuilder
,我以后会尝试使用它。