在一个大的.NET字符串中,最快的计算换行符数量的方法是什么?

15

有办法改善这个吗:

private static int CountNewlines(string s)
{
    int len = s.Length;
    int c = 0;
    for (int i=0; i < len;  i++)
    {
        if (s[i] == '\n') c++;
    }
    return c;
}

我特别关注字符串的Item访问器。 不确定它是否像C/C++一样只是指针算术。


这个字符串从哪里来的? 我猜你关心性能,那么这一定是个大字符串吧? 如果这个大字符串来自文件或者网络服务调用,那么问题应该是“在流中计算换行符的最快方法”。原因是将整个字符串转换成字符串将会很耗费资源。 - Simon
2
顺便说一句,这可能真的是愚人节玩笑,我已经分辨不出来了 :-) - M.A. Hanin
我关心性能,因为第一,它可能很大(32k),第二,在Windows Forms控件的OnPaint()方法中使用它。因此,它被频繁调用。 - Cheeso
@Hanin,为什么这会是愚人节玩笑?这似乎是一个很好的基础问题。 - Cheeso
@Cheeso:如果您的字符串变量被修改,就没有办法仅计算换行符的数量并缓存该结果,而不是在每次绘制时重新计算该值。 - Dirk Vollmar
是的,我正在做这个。但是当我需要计数时,我希望能够快速计算。 - Cheeso
6个回答

21

我测试了这些实现

private static int Count1(string s)
{
    int len = s.Length;
    int c = 0;
    for (int i=0; i < len;  i++)
    {
        if (s[i] == '\n') c++;
    }
    return c+1;
}

private static int Count2(string s)
{
    int count = -1;
    int index = -1;

    do
    {
        count++;
        index = s.IndexOf('\n', index + 1);
    }
    while (index != -1);

    return count+1;
}

private static int Count3(string s)
{
    return s.Count( c => c == '\n' ) + 1;
}


private static int Count4(string s)
{
    int n = 0;
    foreach( var c in s )
    {
        if ( c == '\n' ) n++;
    }
    return n+1;
}

private static int Count5(string s)
{
    var a = s.ToCharArray();
    int c = 0;
    for (int i=0; i < a.Length; i++)
    {
        if (a[i]=='\n') c++;
    }
    return c+1;
}

这是我的100,000次迭代计时结果,适用于一个长度约为25k的字符串。数值越小,速度越快。

              Time  Factor
Count1   4.8581503     1.4
Count2   4.1406059     1.2
Count3  45.3614124    13.4
Count4   3.3896130     1.0
Count5   5.9304543     1.7

出乎我的意料,通过测试,我发现使用 Enumerator 实现是最快的,而且速度相比其他实现方式要快20%。不管函数执行顺序如何,结果都是可重复的。我还使用了预热阶段来确保消除瞬态影响(例如 JIT 等)。

这是在发布构建(/optimize+)下进行的测试。


6

我非常确定这种方法不会比将字符串转换为字节并检查那些字节要慢,如果不是更快的话。String类应该有高度优化。

如果这是一个大字符串,也许通过几个线程的并行执行可以使事情变得更快 :-)


并行执行只会在多核/多处理器机器上加速 - 但对于非常大的字符串可能是一个选项。 - LBushkin
当然,创建的线程数没有超过总核心数是没有意义的。 如果这个字符串包含许多换行符,则可能只是一个非常大的文本文件之类的东西,因此OP实际上可以使用此选项... - M.A. Hanin

4

这可能是最有效的选择 - 项目访问器在内部进行了优化,您可以将其视为执行指针算术运算。


2

嗯,String 实现了 IEnumerable<char>,所以我肯定会尝试:

s.Count( c => c == '\n' )

虽然这看起来很好,但原始方法快了30倍 :)

我还没有放弃IEnumerable,所以我也尝试过:

int n = 0;
foreach( var c in s )
{
    if ( c == '\n' ) n++;
}
return n;

看起来速度与原始方法一样快。


0
你可以使用"ToCharArray();"将字符串转换为字符数组,但我认为这不会提高性能。你可以尝试使用不安全的代码(指针)代替for循环,但是这也有其缺点。

好的,我可以这样做。我还没有尝试过。这是一个很长的字符串,并且会被反复调用,所以我倾向于避免创建新的数组来计数。 - Cheeso
这个函数会被多次调用同一个字符串吗?你可以使用一个字典,以弱引用作为键,以整数作为结果。这样你就可以缓存结果了。 - Peter

0
如果你会在循环中使用它,将其变成实例方法。

比如在使用System.IO时,调用file.Copy和new FileInfo.. .Copy()。这是来自O'Reilly C# Cookbook的内容。 - Snoop Dogg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接