字符串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创建一个字符串:
- 使用
StringBuilder
并重复调用其Append(char ch)
方法。 - 使用
string.Concat
- 使用
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 ; } } }