更新
根据Mr Cheese的回答,似乎
public static string Join<T>(string separator, IEnumerable<T> values)
string.Join
方法的重载版本通过使用StringBuilderCache
类获得其优势。
有没有人对这个说法的正确性或原因有任何反馈?
我能否编写自己的重载版本呢?
public static string Join<T>(
string separator,
string prefix,
string suffix,
IEnumerable<T> values)
使用StringBuilderCache
类的函数是什么?
提交我的回答后,我陷入了一些分析中,以确定哪个答案的性能最佳。
我编写了以下代码,它位于控制台Program
类中,用于测试我的想法。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
class Program
{
static void Main()
{
const string delimiter = ",";
const string prefix = "[";
const string suffix = "]";
const int iterations = 1000000;
var sequence = Enumerable.Range(1, 10).ToList();
Func<IEnumerable<int>, string, string, string, string>[] joiners =
{
Build,
JoinFormat,
JoinConcat
};
// Warmup
foreach (var j in joiners)
{
Measure(j, sequence, delimiter, prefix, suffix, 5);
}
// Check
foreach (var j in joiners)
{
Console.WriteLine(
"{0} output:\"{1}\"",
j.Method.Name,
j(sequence, delimiter, prefix, suffix));
}
foreach (var result in joiners.Select(j => new
{
j.Method.Name,
Ms = Measure(
j,
sequence,
delimiter,
prefix,
suffix,
iterations)
}))
{
Console.WriteLine("{0} time = {1}ms", result.Name, result.Ms);
}
Console.ReadKey();
}
private static long Measure<T>(
Func<IEnumerable<T>, string, string, string, string> func,
ICollection<T> source,
string delimiter,
string prefix,
string suffix,
int iterations)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < iterations; i++)
{
func(source, delimiter, prefix, suffix);
}
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
private static string JoinFormat<T>(
IEnumerable<T> source,
string delimiter,
string prefix,
string suffix)
{
return string.Format(
"{0}{1}{2}",
prefix,
string.Join(delimiter, source),
suffix);
}
private static string JoinConcat<T>(
IEnumerable<T> source,
string delimiter,
string prefix,
string suffix)
{
return string.Concat(
prefix,
string.Join(delimiter, source),
suffix);
}
private static string Build<T>(
IEnumerable<T> source,
string delimiter,
string prefix,
string suffix)
{
var builder = new StringBuilder();
builder = builder.Append(prefix);
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
builder.Append(e.Current);
}
while (e.MoveNext())
{
builder.Append(delimiter);
builder.Append(e.Current);
}
}
builder.Append(suffix);
return builder.ToString();
}
}
在命令行中以发布配置和优化模式运行代码,我得到了以下输出:
...
生成时间=1555毫秒
JoinFormat 时间=1715毫秒
JoinConcat 时间=1452毫秒
对我来说唯一的惊喜是 Join-Format 组合最慢。考虑了这个答案之后,这就显得有些合理了。string.Join 的输出由 string.Format 中的外部 StringBuilder 处理,这种方法固有的延迟让人不解。
经过思考,我仍然不太明白为什么 string.Join 可以更快。我已经阅读了有关它使用 FastAllocateString() 的资料,但不明白如何在调用 sequence 的每个成员的 ToString() 方法之前准确地预分配缓冲区。为什么 Join-Concat 组合更快呢?
一旦我明白了这一点,是否可能编写自己的 unsafe string Join 函数,接受额外的 prefix 和 suffix 参数,比“安全”的替代方案表现更好。
我尝试了几次,虽然它们可以工作,但并没有更快。
string.Concat
等同于仅使用+
运算符:prefix + string.Join(delimiter, source) + suffix
。 - Jon Skeetfunc(source, delimiter, prefix, suffix);
一次(以避免JIT问题)。 - L.BMain()
中\\Warmup
的目的。 - Jodrell