我正在读取CSV文件的每一行,需要获取每列中的各个值。所以现在我只是在使用:
values = line.Split(delimiter);
其中line
是一个字符串,用于保存分隔符分隔的值。
衡量ReadNextRow
方法的效果我注意到它在String.Split
上花费了66%,所以我想知道是否有人知道更快的方法来执行此操作。
谢谢!
答案 0 :(得分:21)
string.Split的BCL实现实际上非常快,我在这里做了一些测试,试图预先形成它并且这并不容易。
但是你可以做的一件事就是将其作为一个发电机来实现:
public static IEnumerable<string> GetSplit( this string s, char c )
{
int l = s.Length;
int i = 0, j = s.IndexOf( c, 0, l );
if ( j == -1 ) // No such substring
{
yield return s; // Return original and break
yield break;
}
while ( j != -1 )
{
if ( j - i > 0 ) // Non empty?
{
yield return s.Substring( i, j - i ); // Return non-empty match
}
i = j + 1;
j = s.IndexOf( c, i, l - i );
}
if ( i < l ) // Has remainder?
{
yield return s.Substring( i, l - i ); // Return remaining trail
}
}
对于小字符串,上面的方法不一定比string.Split更快,但它会在找到它们时返回结果,这是延迟评估的强大功能。如果您排长队或需要节省内存,这就是您的选择。
上面的方法受到IndexOf和Substring的性能的限制,它执行了超出范围检查的索引,并且需要更快地优化它们并实现自己的帮助方法。你可以击败string.Split性能,但它会采取切割器int-hacking。您可以阅读我关于here的帖子。
答案 1 :(得分:18)
应该指出的是split()
是解析CSV文件的一种可疑方法,以防你在文件中遇到逗号,例如:
1,"Something, with a comma",2,3
在不知道你如何分析的情况下,我要指出的另一件事是要小心分析这种低级细节。 Windows / PC计时器的粒度可能会发挥作用,您可能在循环中有很大的开销,因此请使用某种控制值。
话虽这么说,split()
是为处理正则表达式而构建的,这显然比你需要的更复杂(并且无论如何都是处理转义逗号的错误工具)。此外,split()
会创建大量临时对象。
因此,如果你想加快速度(而且我很难相信这部分的性能真的是一个问题),那么你想要手工完成它并且你想要重用你的缓冲区对象,这样你就不会经常创建对象并让垃圾收集器清理它们。
该算法相对简单:
哦,为了让你对正则表达式的成本有所了解,有一个问题(Java不是C#,但原理是相同的),有人想用字符串替换每个第n个字符。我建议在String上使用replaceAll()
。 Jon Skeet手动编码循环。出于好奇,我对两个版本进行了比较,并且他的表现要好一些。
所以,如果你真的想要表现,那就是时候解析了。
或者,更好的是,使用其他人的优化解决方案,例如fast CSV reader。
顺便说一句,虽然这与Java有关,但它涉及正则表达式(通用)和replaceAll()
与手动编码循环的表现:Putting char into a java string for each N characters。
答案 2 :(得分:4)
根据使用情况,您可以使用Pattern.split而不是String.split来加快速度。如果你在循环中有这个代码(我假设你可能这样做,因为它听起来你正在从文件中解析行)String.split(String regex)每次循环语句时都会调用你的regex字符串上的Pattern.compile执行。为了优化这一点,Pattern.compile一旦在循环外部模式,然后使用Pattern.split,在循环内传递你想要分割的行。
希望这有帮助
答案 3 :(得分:3)
这是一个使用ReadOnlySpan的非常基本的示例。在我的机器上,这大约需要150ns,而不是string.Split()大约需要250ns。那真是一个不错的40%的改善。
string serialized = "1577836800;1000;1";
ReadOnlySpan<char> span = serialized.AsSpan();
Trade result = new Trade();
index = span.IndexOf(';');
result.UnixTimestamp = long.Parse(span.Slice(0, index));
span = span.Slice(index + 1);
index = span.IndexOf(';');
result.Price = float.Parse(span.Slice(0, index));
span = span.Slice(index + 1);
index = span.IndexOf(';');
result.Quantity = float.Parse(span.Slice(0, index));
return result;
请注意,ReadOnlySpan.Split()将很快成为框架的一部分。看到 https://github.com/dotnet/runtime/pull/295
答案 4 :(得分:2)
我发现这个实现比Dejan Pelzel's blog快了30%。我从那里开始:
解决方案
考虑到这一点,我设置创建一个字符串拆分器,它将使用类似于StringBuilder的内部缓冲区。它使用非常简单的逻辑来完成字符串并将值部分保存到缓冲区中。
public int Split(string value, char separator)
{
int resultIndex = 0;
int startIndex = 0;
// Find the mid-parts
for (int i = 0; i < value.Length; i++)
{
if (value[i] == separator)
{
this.buffer[resultIndex] = value.Substring(startIndex, i - startIndex);
resultIndex++;
startIndex = i + 1;
}
}
// Find the last part
this.buffer[resultIndex] = value.Substring(startIndex, value.Length - startIndex);
resultIndex++;
return resultIndex;
如何使用
StringSplitter类的使用非常简单,如下例所示。只是要小心重用StringSplitter对象,而不是在循环中创建它的新实例或一次性使用它。在这种情况下,最好使用内置的String.Split。
var splitter = new StringSplitter(2);
splitter.Split("Hello World", ' ');
if (splitter.Results[0] == "Hello" && splitter.Results[1] == "World")
{
Console.WriteLine("It works!");
}
Split方法返回找到的项目数,因此您可以轻松地迭代结果:
var splitter = new StringSplitter(2);
var len = splitter.Split("Hello World", ' ');
for (int i = 0; i < len; i++)
{
Console.WriteLine(splitter.Results[i]);
}
这种方法有优点和缺点。
答案 5 :(得分:1)
你可能认为有优化,但实际情况是你会在其他地方付钱。
例如,你可以自己进行拆分并遍历所有字符并在遇到它时处理每一列,但无论如何你都要复制字符串的所有部分。例如,我们在C或C ++中可以做的一个优化是用'\ 0'字符替换所有分隔符,并保持指向列开头的指针。然后,我们不必复制所有字符串数据只是为了获取它的一部分。但是你不能用C#做,也不想做。
如果源中的列数与您需要的列数之间存在很大差异,则手动遍历字符串可能会产生一些好处。但是,这种好处会花费你时间来开发和维护它。
我被告知,90%的CPU时间花费在10%的代码上。这个“真相”有变化。在我看来,如果处理CSV是您的应用需要做的事情,那么在斯普利特花费66%的时间并不是那么糟糕。
戴夫
答案 6 :(得分:0)
Some very thorough analysis on String.Slit() vs Regex and other methods.
我们正在讨论非常大的字符串上的ms节省。
答案 7 :(得分:0)
String.Split的主要问题(?)是它的一般性,因为它满足了许多需求。
如果你比分析更了解你的数据,它可以改进你自己的。
例如,如果:
如果其中任何一个属实,您可能会通过编写自己更具体的String.Split版本来看到改进。
话虽如此,你应该问的第一个问题是,这实际上是否值得解决。读取和导入文件所花费的时间是否真的让您觉得这可以很好地利用您的时间?如果没有,那么我会不管它。
第二个问题是为什么与其他代码相比,String.Split使用了那么多时间。如果答案是代码对数据做的很少,那么我可能不会打扰。
但是,如果您将数据填充到数据库中,那么在String.Split中花费的代码的66%会构成一个大问题。
答案 8 :(得分:0)
CSV解析实际上是非常复杂的,我使用基于包装ODBC Text驱动程序的类,而且只有我必须这样做。
上面推荐的ODBC解决方案乍一看基本上是相同的方法。
我完全建议你做一些关于CSV解析的研究,然后再走近一条几乎没有完全工作的路径(太常见了)。只有双引号字符串需要的Excel才是我经验中最棘手的事情之一。
答案 9 :(得分:0)
正如其他人所说,String.Split()
并不总能与CSV文件一起使用。考虑一个如下所示的文件:
"First Name","Last Name","Address","Town","Postcode"
David,O'Leary,"12 Acacia Avenue",London,NW5 3DF
June,Robinson,"14, Abbey Court","Putney",SW6 4FG
Greg,Hampton,"",,
Stephen,James,"""Dunroamin"" 45 Bridge Street",Bristol,BS2 6TG
(例如,语音标记的不一致使用,包括逗号和语音标记的字符串等)
这个CSV阅读框架将处理所有这些,并且也非常有效:
答案 10 :(得分:0)
这是我的解决方案:
Public Shared Function FastSplit(inputString As String, separator As String) As String()
Dim kwds(1) As String
Dim k = 0
Dim tmp As String = ""
For l = 1 To inputString.Length - 1
tmp = Mid(inputString, l, 1)
If tmp = separator Then k += 1 : tmp = "" : ReDim Preserve kwds(k + 1)
kwds(k) &= tmp
Next
Return kwds
End Function
以下是基准测试的版本:
Public Shared Function FastSplit(inputString As String, separator As String) As String()
Dim sw As New Stopwatch
sw.Start()
Dim kwds(1) As String
Dim k = 0
Dim tmp As String = ""
For l = 1 To inputString.Length - 1
tmp = Mid(inputString, l, 1)
If tmp = separator Then k += 1 : tmp = "" : ReDim Preserve kwds(k + 1)
kwds(k) &= tmp
Next
sw.Stop()
Dim fsTime As Long = sw.ElapsedTicks
sw.Start()
Dim strings() As String = inputString.Split(separator)
sw.Stop()
Debug.Print("FastSplit took " + fsTime.ToString + " whereas split took " + sw.ElapsedTicks.ToString)
Return kwds
End Function
以下是相对较小的字符串的一些结果,但具有不同的大小,最多8kb块。 (时间在滴答声中)
FastSplit花了8分,而分裂花费了10分
FastSplit花了214,而分裂花了216
FastSplit需要10分钟,而分裂需要12分钟
FastSplit占了8分,而分裂则占了9分
FastSplit花了8分,而分裂花费了10分
FastSplit需要10分钟,而分裂需要12分钟
FastSplit占了7分,而分裂则占了9分
FastSplit占用了6分,而分裂占了8分
FastSplit占用了5分,而分裂则占了7分
FastSplit需要10分,而分裂需要13分
FastSplit占了9分,而分裂占了232
FastSplit占用了7分,而分裂占了8分
FastSplit占了8分,而分裂则占了9分
FastSplit花了8分,而分裂花费了10分
FastSplit花了215,而分裂花了217
FastSplit花费了10分钟,而分裂花费了231
FastSplit花了8分,而分裂花费了10分
FastSplit花了8分,而分裂花费了10分
FastSplit占了7分,而分裂则占了9分
FastSplit花了8分,而分裂花费了10分
FastSplit花费了10分钟,而分裂花了1405
FastSplit占用了9分,而分裂占了11分
FastSplit花了8分,而分裂花费了10分
另外,我知道有人会阻止我使用ReDim Preserve而不是使用列表...原因是,列表确实没有在我的基准测试中提供任何速度差异所以我回到了“简单”的方式
答案 11 :(得分:0)
public static unsafe List<string> SplitString(char separator, string input)
{
List<string> result = new List<string>();
int i = 0;
fixed(char* buffer = input)
{
for (int j = 0; j < input.Length; j++)
{
if (buffer[j] == separator)
{
buffer[i] = (char)0;
result.Add(new String(buffer));
i = 0;
}
else
{
buffer[i] = buffer[j];
i++;
}
}
buffer[i] = (char)0;
result.Add(new String(buffer));
}
return result;
}
答案 12 :(得分:-1)
你可以假设String.Split接近最佳状态;也就是说它很难改进。到目前为止,更容易的解决方案是检查是否需要拆分字符串。您很可能会直接使用单个字符串。如果定义StringShim类(引用String,begin&amp; end index),您将能够将String拆分为一组填充程序。这些将具有小的固定大小,并且不会导致字符串数据副本。
答案 13 :(得分:-2)
String.split
相当慢,如果你想要一些更快的方法,你可以去。 :)
然而,基于规则的解析器可以更好地解析CSV。
这家伙已经为java制作了一个基于规则的标记器。 (遗憾的是需要一些复制和粘贴)
http://www.csdgn.org/code/rule-tokenizer
private static final String[] fSplit(String src, char delim) {
ArrayList<String> output = new ArrayList<String>();
int index = 0;
int lindex = 0;
while((index = src.indexOf(delim,lindex)) != -1) {
output.add(src.substring(lindex,index));
lindex = index+1;
}
output.add(src.substring(lindex));
return output.toArray(new String[output.size()]);
}
private static final String[] fSplit(String src, String delim) {
ArrayList<String> output = new ArrayList<String>();
int index = 0;
int lindex = 0;
while((index = src.indexOf(delim,lindex)) != -1) {
output.add(src.substring(lindex,index));
lindex = index+delim.length();
}
output.add(src.substring(lindex));
return output.toArray(new String[output.size()]);
}