将IEnumerable <char>转换为字符串的最佳方法?

Shi*_*mmy 39 .net regex performance performance-testing

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

例如:

var x = "asdf1234";
var y = new string(x.TakeWhile(char.IsLetter).ToArray());
Run Code Online (Sandbox Code Playgroud)

是不是有更好的转换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();
  }
}
Run Code Online (Sandbox Code Playgroud)

结果:

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%).
Run Code Online (Sandbox Code Playgroud)

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

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

Kai*_*i G 43

怎么样转换IEnumerable<char>string:

string.Concat(x.TakeWhile(char.IsLetter));
Run Code Online (Sandbox Code Playgroud)

  • +1非常短,不需要.ToArray() (4认同)

Jod*_*ell 21

编辑发布.Net Core 2.1

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

"Concat"的1000000次迭代耗时842ms.

"新字符串"的1000000次迭代耗时1009ms.

"sb"的1000000次迭代耗时902ms.

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

有关详细信息,请参阅MS博客文章.


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

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

新字符串

return new string(charSequence.ToArray());
Run Code Online (Sandbox Code Playgroud)

CONCAT

return string.Concat(charSequence)
Run Code Online (Sandbox Code Playgroud)

StringBuilder的

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

return sb.ToString();
Run Code Online (Sandbox Code Playgroud)

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

"Concat"的1000000次迭代耗时1597ms.

"新字符串"的1000000次迭代耗时869ms.

"StringBuilder"的1000000次迭代耗时748ms.

这告诉我,没有充分的理由使用string.Concat这项任务.如果你想要简单,请使用新的字符串方法,如果想要性能,请使用StringBuilder.

我会告诫我的断言,在实践中所有这些方法都运行良好,这可能都是过度优化.

  • 我想牺牲 121 毫秒来使用 `new string`,而不是编写额外的三行代码来使用 `StringBuilder`。#cleanCode。 (3认同)

Luk*_*keH 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1预分配缓冲区可能是使它更快的原因,但这只是一个猜测-有限的测试表明它的方式比使用Substring()更快。 (2认同)

Mer*_*ham 13

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

有可能的.你在问题本身中做到了:

var y = new string(x.TakeWhile(char.IsLetter).ToArray());
Run Code Online (Sandbox Code Playgroud)

是不是有更好的方法转换IEnumerable<char>为字符串?

(我的假设是:)

框架没有这样的构造函数,因为字符串是不可变的,并且您必须遍历枚举两次才能为字符串预分配内存.这并不总是一个选项,特别是如果您的输入是一个流.

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

通过要求用户使用ToArray扩展方法,可以容易地解决这些问题.

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

  • 匿名的downvoters没有任何帮助.说明您的理由,我将解决您的疑虑. (7认同)

Bro*_*ass 9

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

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

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
Run Code Online (Sandbox Code Playgroud)

  • +1.将它包装在某种辅助方法中以便重复使用并没有错.像`source.LeadingLettersOnly()`这样的东西比`new string(source.TakeWhile(char.IsLetter).ToArray())`,imo更具可读性. (3认同)

Vla*_*adu 5

return new string(foo.Select(x => x).ToArray());
Run Code Online (Sandbox Code Playgroud)