为什么不能在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
这是第一次运行时最慢的。{/ p>
无论如何,我的问题是,是否有任何方法可以通过重新调整TakeWhile
函数的结果来优化性能。
答案 0 :(得分:38)
如何将IEnumerable<char>
转换为string
:
string.Concat(x.TakeWhile(char.IsLetter));
答案 1 :(得分:17)
编辑发布.Net Core 2.1
重复测试.Net Core 2.1的发布,我得到了这样的结果
&#34; Concat&#34; 1000000次迭代花了842ms。
&#34;新字符串&#34; 1000000次迭代花了1009ms。
&#34; sb&#34; 1000000次迭代花了902ms。
简而言之,如果您使用.Net Core 2.1或更高版本,则Concat
为王。
有关详细信息,请参阅MS blog post。
我已将此作为another question的主题,但越来越多,这正在成为这个问题的直接答案。
我已经对将IEnumerable<char>
转换为string
的3种简单方法进行了一些性能测试,这些方法是
新字符串
return new string(charSequence.ToArray());
<强>的毗连强>
return string.Concat(charSequence)
<强>的StringBuilder 强>
var sb = new StringBuilder();
foreach (var c in charSequence)
{
sb.Append(c);
}
return sb.ToString();
在我的测试中,详细信息在linked question中,针对1000000
的{{1}}次迭代,我得到了这样的结果,
&#34; Concat&#34; 1000000次迭代花了1597ms。
&#34;新字符串&#34; 1000000次迭代花了869ms。
&#34; StringBuilder&#34; 1000000次迭代花了748ms。
这告诉我,没有充分的理由使用"Some reasonably small test data"
来执行此任务。如果您想要简单,请使用 new string 方法,如果想要性能,请使用 StringBuilder 。
我会告诫我的断言,在实践中所有这些方法都运行良好,这可能都是过度优化。
答案 2 :(得分: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);
}
}
答案 3 :(得分:13)
为什么不能在字符串上使用流利的语言?
有可能。你是在问题本身中做到的:
var y = new string(x.TakeWhile(char.IsLetter).ToArray());
是否有更好的方法将
IEnumerable<char>
转换为字符串?
(我的假设是:)
框架没有这样的构造函数,因为字符串是不可变的,并且您必须遍历枚举两次才能为字符串预分配内存。这并不总是一个选项,特别是如果您的输入是一个流。
唯一的解决方案是首先推送到支持数组或StringBuilder
,然后在输入增长时重新分配。对于像字符串一样低级的东西,这可能应该被视为过于隐藏的一种机制。它还会通过鼓励人们使用不能尽可能快的机制将perf问题推入字符串类。
通过要求用户使用ToArray
扩展方法,可以轻松解决这些问题。
正如其他人所指出的,如果你编写支持代码,你可以实现你想要的(perf 和表达代码),并将这个支持代码包装在扩展方法中以获得一个干净的界面。 / p>
答案 4 :(得分:9)
你经常可以做得更好。但这会给你带来什么?除非这确实是您应用程序的瓶颈,并且您已经测量过它,我会坚持使用Linq TakeWhile()
版本:它是最易读和可维护的解决方案,这对大多数应用程序来说都是重要的。
如果真的正在寻找原始性能,您可以手动进行转换 - 在我的测试中,以下内容大约是4+(取决于输入字符串长度)的因素TakeWhile()
- 但我不会亲自使用它,除非它很关键:
int j = 0;
for (; j < input.Length; j++)
{
if (!char.IsLetter(input[j]))
break;
}
string output = input.Substring(0, j);
答案 5 :(得分:5)
return new string(foo.Select(x => x).ToArray());
答案 6 :(得分:1)
这个答案试图结合已经提供的优秀答案的以下方面。
为此使用了 IEnumerable<char>
上的扩展方法。
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
}
这涵盖了所有基础。
可读性很强:
var y = x.TakeWhile(char.IsLetter).Join();
如果将来有首选的新方法,则可以通过更改一个代码块来更新所有转换。
它支持基于当前正在编译的 .NET 版本的当前最佳性能实现。