[C#] C# String.Format() 和 StringBuilder
英文原文:https://codewithshadman.com/c-stringformat-and-stringbuilder/
最近,我看到一些代码,看起来像这样:
StringBuilder builder = new StringBuilder();
builder.Append(String.Format("{0} {1}", firstName, lastName));
// Do more with builder...
现在,我不想争论 String.Concat() 在这里如何提高性能。 String.Format() 允许更轻松地本地化代码,这里就使用它来实现此目的。真正的问题是应该使用 StringBuilder.AppendFormat() 来代替:
StringBuilder builder = new StringBuilder();
builder.AppendFormat("{0} {1}", firstName, lastName);
// Do more with builder...
这很重要的原因是,在内部,String.Format() 实际上创建了一个 StringBuilder 并调用 StringBuilder.AppendFormat()! String.Format() 的实现如下:
public static string Format(IFormatProvider provider, string format, params object[] args)
{
if (format == null || args == null)
throw new ArgumentNullException((format == null ? "format" : "args"));
StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
builder.AppendFormat(provider, format, args);
return builder.ToString();
}
您可以在.NET Core运行时的源代码中看到实际的实现。这是相同的链接:String.Manipulation.cs
在 String.Manipulation.cs 文件中,您将找到 Format 方法:
从上面的代码中,可以看到Format方法调用了一个内部方法FormatHelper。 现在,FormatHelper 方法使用 StringBuilder 通过 StringBuilderCache 格式化文本。
事实证明,格式化逻辑实际上是在StringBuilder.AppendFormat()中实现的。因此,原始代码实际上导致在不需要时使用第二个 StringBuilder。
如果您试图避免通过使用 String.Format() 连接字符串来创建 StringBuilder,了解这一点也很重要。例如:
string nameString = "<td>" + String.Format("{0} {1}", firstName, lastName) + "</td>"
+ "<td>" + String.Format("{0}, {1}", id, department) + "</td>";
如果格式化字符串的大小大于 StringBuilderCache 中使用的 MaxBuilderSize(设置为 360),该代码实际上会创建两个 StringBuilder。因此,创建一个 StringBuilder 并使用 AppendFormat() 会更高效:
StringBuilder nameBuilder = new StringBuilder();
nameBuilder.Append("<td>");
nameBuilder.AppendFormat("{0} {1}", firstName, lastName);
nameBuilder.Append("</td>");
nameBuilder.Append("<td>");
nameBuilder.AppendFormat("{0}, {1}", id, department);
nameBuilder.Append("</td>");
string nameString = nameBuilder.ToString();
我决定进行一些性能测试来验证我的说法。首先,我对代码进行了计时,演示了 StringBuilder 存在的原因:
const int LOOP_SIZE = 10000;
const string firstName = "Shadman";
const string lastName = "Kudchikar";
const int id = 1;
const string department = ".NET Team";
static void PerformanceTest1()
{
string nameString = String.Empty;
for (int i = 0; i < LOOP_SIZE; i++)
nameString += String.Format("{0} {1}", firstName, lastName);
}
上面的代码创建一个新字符串,然后在 for 循环内将其连接起来。这会导致每次传递时创建两个新字符串——一个来自 String.Format(),另一个来自串联。这是非常低效的。
接下来,我测试了修改为使用带有 String.Format() 的 StringBuilder 的相同代码:
static void PerformanceTest2()
{
StringBuilder builder = new StringBuilder((firstName.Length + lastName.Length + 1) * LOOP_SIZE);
for (int i = 0; i < LOOP_SIZE; i++)
builder.Append(String.Format("{0} {1}", firstName, lastName));
string nameString = builder.ToString();
}
最后,我测试了使用 StringBuilder.AppendFormat() 而不是 String.Format() 的代码:
static void PerformanceTest3()
{
StringBuilder builder = new StringBuilder((firstName.Length + lastName.Length + 1) * LOOP_SIZE);
for (int i = 0; i < LOOP_SIZE; i++)
builder.AppendFormat("{0} {1}", firstName, lastName);
string nameString = builder.ToString();
}
这三种方法的运行时间如下:
对于 .NET Framework:
PerformanceTest1: 0.21045 seconds PerformanceTest2: 0.001585 seconds PerformanceTest3: 0.0010846 seconds
对于 .NET Core:
PerformanceTest1: 0.173821 seconds PerformanceTest2: 0.0012753 seconds PerformanceTest3: 0.0007812 seconds
显然,在不使用 StringBuilder 的情况下在循环中连接字符串的效率低得惊人。而且,删除对 String.Format 的调用还可以提高性能。
接下来我测试了以下两种方法:
static void PerformanceTest4()
{
string htmlString;
for (int i = 0; i < LOOP_SIZE; i++)
htmlString = "<td>" + String.Format("{0} {1}", firstName, lastName) + "</td><td>"
+ String.Format("{0}, {1}", id, department) + "</td>";
}
static void PerformanceTest5()
{
StringBuilder builder = new StringBuilder(256);
string htmlString;
for (int i = 0; i < LOOP_SIZE; i++)
{
builder.Append("<td>");
builder.AppendFormat("{0} {1}", firstName, lastName);
builder.Append("</td><td>");
builder.AppendFormat("{0}, {1}", id, department);
builder.Append("</td>");
htmlString = builder.ToString();
builder.Length = 0;
}
}
这两种方法的运行时间如下:
对于 .NET Framework:
PerformanceTest4: 0.0050095 seconds PerformanceTest5: 0.0044467 seconds
对于 .NET Core:
PerformanceTest4: 0.0036363999999999997 seconds PerformanceTest5: 0.0019971 seconds
正如您所看到的,了解何时使用 String.Format 以及何时使用 StringBuilder.AppendFormat() 非常重要。虽然可以实现的性能提升相当小,但它们太容易编码了。
您可以在此处下载性能测试:https://github.com/kudchikarsk/string-format-performance-test。