49

为什么不能使用流利的语言string

例如:

var x = "asdf1234";
var y = new string(x.TakeWhile(char.IsLetter).ToArray());

难道没有更好的转换IEnumerable<char>方式string吗?

这是我做的一个测试:

class Program
{
  static string input = "asdf1234";
  static void Main()
  {
    Console.WriteLine("1000 times:");
    RunTest(1000, input);
    Console.WriteLine("10000 times:");
    RunTest(10000,input);
    Console.WriteLine("100000 times:");
    RunTest(100000, input);
    Console.WriteLine("100000 times:");
    RunTest(100000, "ffff57467");


    Console.ReadKey();

  }

  static void RunTest( int times, string input)
  {

    Stopwatch sw = new Stopwatch();

    sw.Start();
    for (int i = 0; i < times; i++)
    {
      string output = new string(input.TakeWhile(char.IsLetter).ToArray());
    }
    sw.Stop();
    var first = sw.ElapsedTicks;

    sw.Restart();
    for (int i = 0; i < times; i++)
    {
      string output = Regex.Match(input, @"^[A-Z]+", 
        RegexOptions.IgnoreCase).Value;
    }
    sw.Stop();
    var second = sw.ElapsedTicks;

    var regex = new Regex(@"^[A-Z]+", 
      RegexOptions.IgnoreCase);
    sw.Restart();
    for (int i = 0; i < times; i++)
    {
      var output = regex.Match(input).Value;
    }
    sw.Stop();
    var third = sw.ElapsedTicks;

    double percent = (first + second + third) / 100;
    double p1 = ( first / percent)/  100;
    double p2 = (second / percent )/100;
    double p3 = (third / percent  )/100;


    Console.WriteLine("TakeWhile took {0} ({1:P2}).,", first, p1);
    Console.WriteLine("Regex took {0}, ({1:P2})." , second,p2);
    Console.WriteLine("Preinstantiated Regex took {0}, ({1:P2}).", third,p3);
    Console.WriteLine();
  }
}

结果:

1000 times:
TakeWhile took 11217 (62.32%).,
Regex took 5044, (28.02%).
Preinstantiated Regex took 1741, (9.67%).

10000 times:
TakeWhile took 9210 (14.78%).,
Regex took 32461, (52.10%).
Preinstantiated Regex took 20669, (33.18%).

100000 times:
TakeWhile took 74945 (13.10%).,
Regex took 324520, (56.70%).
Preinstantiated Regex took 172913, (30.21%).

100000 times:
TakeWhile took 74511 (13.77%).,
Regex took 297760, (55.03%).
Preinstantiated Regex took 168911, (31.22%).

结论:我怀疑什么是更好的选择,我想我会继续,TakeWhile只有在第一次运行时最慢。

无论如何,我的问题是是否有任何方法可以通过重新排列函数的结果来优化性能TakeWhile

4

8 回答 8

51

如何将其转换IEnumerable<char>string

string.Concat(x.TakeWhile(char.IsLetter));
于 2012-08-29T05:56:00.350 回答
29

针对 .Net Core 2.1 的发布进行了编辑

重复测试.Net Core 2.1的发布,我得到这样的结果

“Concat”的 1000000 次迭代耗时 842 毫秒。

“新字符串”的 1000000 次迭代耗时 1009 毫秒。

“sb”的 1000000 次迭代耗时 902 毫秒。

简而言之,如果您使用的是 .Net Core 2.1 或更高版本,Concat则为王。


我已经将此作为另一个问题的主题,但越来越多,这正在成为这个问题的直接答案。

我已经对 3 种将 a 转换IEnumerable<char>为 a的简单方法进行了一些性能测试string,这些方法是

新字符串

return new string(charSequence.ToArray());

康卡特

return string.Concat(charSequence)

字符串生成器

var sb = new StringBuilder();
foreach (var c in charSequence)
{
    sb.Append(c);
}

return sb.ToString();

在我的测试中,在链接的问题中有详细说明,对于我得到这样的结果的1000000迭代,"Some reasonably small test data"

“Concat”的 1000000 次迭代耗时 1597 毫秒。

“新字符串”的 1000000 次迭代耗时 869 毫秒。

“StringBuilder”的 1000000 次迭代耗时 748 毫秒。

这向我表明,没有充分的理由使用string.Concat此任务。如果您想要简单,请使用新的字符串方法,如果想要性能,请使用StringBuilder

我会警告我的断言,实际上所有这些方法都可以正常工作,这可能都是过度优化。

于 2015-10-15T07:23:06.800 回答
15

假设您主要寻找性能,那么这样的事情应该比您的任何示例都快得多:

string x = "asdf1234";
string y = x.LeadingLettersOnly();

// ...

public static class StringExtensions
{
    public static string LeadingLettersOnly(this string source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        if (source.Length == 0)
            return source;

        char[] buffer = new char[source.Length];
        int bufferIndex = 0;

        for (int sourceIndex = 0; sourceIndex < source.Length; sourceIndex++)
        {
            char c = source[sourceIndex];

            if (!char.IsLetter(c))
                break;

            buffer[bufferIndex++] = c;
        }
        return new string(buffer, 0, bufferIndex);
    }
}
于 2011-11-13T00:09:51.657 回答
13

为什么不能在字符串上使用流利的语言?

有可能的。您在问题本身中做到了:

var y = new string(x.TakeWhile(char.IsLetter).ToArray());

难道没有更好的方法来转换IEnumerable<char>为字符串吗?

(我的假设是:)

该框架没有这样的构造函数,因为字符串是不可变的,您必须遍历枚举两次才能为字符串预分配内存。这并不总是一种选择,尤其是当您的输入是流时。

唯一的解决方案是推送到支持数组或StringBuilder首先,并随着输入的增长重新分配。对于像字符串这样低级的东西,这可能应该被认为是一种过于隐藏的机制。它还将通过鼓励人们使用一种不可能尽可能快的机制将性能问题推到字符串类中。

ToArray通过要求用户使用扩展方法可以轻松解决这些问题。

正如其他人指出的那样,如果您编写支持代码,您可以实现您想要的(性能表达代码),并将该支持代码包装在扩展方法中以获得干净的界面。

于 2011-11-13T02:47:57.070 回答
9

您通常可以在性能方面做得更好。但这能给你带来什么?除非这确实是您的应用程序的瓶颈并且您已经测量到它是我会坚持使用 LinqTakeWhile()版本:它是最易读和可维护的解决方案,这对大多数应用程序来说都很重要。

如果您真的在寻找原始性能,您可以手动进行转换 - 以下是大约 4+ 倍(取决于输入字符串长度),比TakeWhile()我的测试快 - 但我不会亲自使用它,除非它很关键:

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
于 2011-11-13T00:13:32.403 回答
7
return new string(foo.Select(x => x).ToArray());
于 2016-08-05T12:54:09.367 回答
1

该答案旨在结合已提供的出色答案的以下方面。

  1. 可读
  2. 面向未来/易于重构
  3. 快速地

为此,IEnumerable<char>使用了扩展方法 on。

public static string Join(this IEnumerable<char> chars)
{
#if NETCOREAPP2_1_OR_GREATER
    return String.Concat(chars);
#else
    var sb = new System.Text.StringBuilder();
    foreach (var c in chars)
    {
        sb.Append(c);
    }

    return sb.ToString();
#endif
}

这涵盖了所有基础。

  1. 它非常易读:

    var y = x.TakeWhile(char.IsLetter).Join();

  2. 如果将来有首选的新方法,则可以通过更改一个代码块来更新所有转换。

  3. 它支持基于当前正在编译的 .NET 版本的当前性能最佳的实现。

于 2021-04-22T17:44:36.093 回答
0

我在带有 BenchmarkDotNet 的 LINQPad 7 (dotnet 6.0.1) 中运行了一些测试:

方法 意思是 错误 标准差
StringFromArray 76.35 微秒 1.482 微秒 1.522 微秒
字符串连接 100.93 微秒 0.675 微秒 0.631 微秒
字符串生成器 100.52 微秒 0.963 微秒 0.901 微秒
字符串生成器聚合 116.80 微秒 1.714 微秒 1.519 微秒

测试代码:

void Main() => BenchmarkRunner.Run<CharsToString>();

public class CharsToString {
    private const int N = 10000;
    private readonly char[] data = new char[N];

    public CharsToString() {
        var random = new Random(42);
        for (var i = 0; i < data.Length; i++) {
            data[i] = (char)random.Next(0, 256);
        }
    }

    [Benchmark]
    public string StringFromArray()
        => new string(data.Where(char.IsLetterOrDigit).ToArray());

    [Benchmark]
    public string StringConcat()
        => string.Concat(data.Where(char.IsLetterOrDigit));

    [Benchmark]
    public string StringBuilder() {
        var sb = new StringBuilder();
        
        foreach (var c in data.Where(char.IsLetterOrDigit))
            sb.Append(c);
        
        return sb.ToString();
    }

    [Benchmark]
    public string StringBuilderAggregate() => data
        .Where(char.IsLetterOrDigit)
        .Aggregate(new StringBuilder(), (sb, c) => sb.Append(c))
        .ToString();
}
于 2022-01-26T11:54:30.583 回答