字符串ctor是将IEnumerable 转换为字符串的最快方法

编辑发布.Net Core 2.1

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

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

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

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

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


编辑了这个问题,以纳入评论中提出的有效点。


我正在思考我对前一个问题的回答 ,我开始怀疑,是这样,

 return new string(charSequence.ToArray()); 

IEnumerable转换为string的最佳方法。 我做了一点搜索,发现这个问题已经在这里问了 。 那个答案断言,

 string.Concat(charSequence) 

是一个更好的选择。 在回答了这个问题之后,还提出了一个StringBuilder枚举方法,

 var sb = new StringBuilder(); foreach (var c in chars) { sb.Append(c); } return sb.ToString(); 

虽然这可能有点笨拙但我把它包括在内是为了完整性。 我决定做一点测试,使用的代码在底部。

当在发布模式下构建时,通过优化,并在没有附加调试器的情况下从命令行运行,我得到这样的结果。

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

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

“sb”的1000000次迭代耗时748ms。

据我推算, new string(...ToArray())速度接近string.Concat方法的两倍。 StringBuilder仍然稍微快一些,但使用起来很尴尬,但可能是一个扩展。

我应该坚持使用new string(...ToArray()) ,还是有什么东西我不见了?

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; class Program { private static void Main() { const int iterations = 1000000; const string testData = "Some reasonably small test data"; TestFunc( chars => new string(chars.ToArray()), TrueEnumerable(testData), 10, "new String"); TestFunc( string.Concat, TrueEnumerable(testData), 10, "Concat"); TestFunc( chars => { var sb = new StringBuilder(); foreach (var c in chars) { sb.Append(c); } return sb.ToString(); }, TrueEnumerable(testData), 10, "sb"); Console.WriteLine("----------------------------------------"); TestFunc( string.Concat, TrueEnumerable(testData), iterations, "Concat"); TestFunc( chars => new string(chars.ToArray()), TrueEnumerable(testData), iterations, "new String"); TestFunc( chars => { var sb = new StringBuilder(); foreach (var c in chars) { sb.Append(c); } return sb.ToString(); }, TrueEnumerable(testData), iterations, "sb"); Console.ReadKey(); } private static TResult TestFunc( Func func, TData testData, int iterations, string stage) { var dummyResult = default(TResult); var stopwatch = Stopwatch.StartNew(); for (var i = 0; i < iterations; i++) { dummyResult = func(testData); } stopwatch.Stop(); Console.WriteLine( "{0} iterations of \"{2}\" took {1}ms.", iterations, stopwatch.ElapsedMilliseconds, stage); return dummyResult; } private static IEnumerable TrueEnumerable(IEnumerable sequence) { foreach (var t in sequence) { yield return t; } } } 

值得注意的是,这些结果虽然从纯粹主义者的角度来看属于IE的情况,但并不总是如此。 例如,如果您实际上有一个char数组,即使您将它作为IEnumerable传递,它也会更快地调用字符串构造函数。

结果:

 Sending String as IEnumerable 10000 iterations of "new string" took 157ms. 10000 iterations of "sb inline" took 150ms. 10000 iterations of "string.Concat" took 237ms. ======================================== Sending char[] as IEnumerable 10000 iterations of "new string" took 10ms. 10000 iterations of "sb inline" took 168ms. 10000 iterations of "string.Concat" took 273ms. 

代码:

 static void Main(string[] args) { TestCreation(10000, 1000); Console.ReadLine(); } private static void TestCreation(int iterations, int length) { char[] chars = GetChars(length).ToArray(); string str = new string(chars); Console.WriteLine("Sending String as IEnumerable"); TestCreateMethod(str, iterations); Console.WriteLine("==========================================================="); Console.WriteLine("Sending char[] as IEnumerable"); TestCreateMethod(chars, iterations); Console.ReadKey(); } private static void TestCreateMethod(IEnumerable testData, int iterations) { TestFunc(chars => new string(chars.ToArray()), testData, iterations, "new string"); TestFunc(chars => { var sb = new StringBuilder(); foreach (var c in chars) { sb.Append(c); } return sb.ToString(); }, testData, iterations, "sb inline"); TestFunc(string.Concat, testData, iterations, "string.Concat"); } 

好吧,我刚刚编写了一个小测试,尝试了3种不同的方法从IEnumerable创建一个字符串:

  1. 使用StringBuilder并重复调用其Append(char ch)方法。
  2. 使用string.Concat
  3. 使用String构造函数。

10,000次迭代生成一个随机的1,000个字符序列并从中构建一个字符串,我在发布版本中看到以下时间:

  • Style = StringBuilder已用时间为00:01:05.9687330分钟。
  • Style = StringConcatFunction经过的时间是00:02:33.2672485分钟。
  • Style = StringConstructor经过的时间是00:04:00.5559091分钟。

StringBuilder明确的赢家。 不过,我正在使用静态StringBuilder (单例)实例。 Dunno,如果这有很大的不同。

这是源代码:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Security.Cryptography; using System.Text; namespace ConsoleApplication6 { class Program { static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create() ; static readonly byte[] buffer = {0,0} ; static char RandomChar() { ushort codepoint ; do { Random.GetBytes(buffer) ; codepoint = BitConverter.ToChar(buffer,0) ; codepoint &= 0x007F ; // restrict to Unicode C0 ; } while ( codepoint < 0x0020 ) ; return (char) codepoint ; } static IEnumerable GetRandomChars( int count ) { if ( count < 0 ) throw new ArgumentOutOfRangeException("count") ; while ( count-- >= 0 ) { yield return RandomChar() ; } } enum Style { StringBuilder = 1 , StringConcatFunction = 2 , StringConstructor = 3 , } static readonly StringBuilder sb = new StringBuilder() ; static string MakeString( Style style ) { IEnumerable chars = GetRandomChars(1000) ; string instance ; switch ( style ) { case Style.StringConcatFunction : instance = String.Concat( chars ) ; break ; case Style.StringBuilder : foreach ( char ch in chars ) { sb.Append(ch) ; } instance = sb.ToString() ; break ; case Style.StringConstructor : instance = new String( chars.ToArray() ) ; break ; default : throw new InvalidOperationException() ; } return instance ; } static void Main( string[] args ) { Stopwatch stopwatch = new Stopwatch() ; foreach ( Style style in Enum.GetValues(typeof(Style)) ) { stopwatch.Reset() ; stopwatch.Start() ; for ( int i = 0 ; i < 10000 ; ++i ) { MakeString( Style.StringBuilder ) ; } stopwatch.Stop() ; Console.WriteLine( "Style={0}, elapsed time is {1}" , style , stopwatch.Elapsed ) ; } return ; } } }