连接字符串最有效的方法是什么?
当前回答
尝试这两段代码,您将找到解决方案。
static void Main(string[] args)
{
StringBuilder s = new StringBuilder();
for (int i = 0; i < 10000000; i++)
{
s.Append( i.ToString());
}
Console.Write("End");
Console.Read();
}
Vs
static void Main(string[] args)
{
string s = "";
for (int i = 0; i < 10000000; i++)
{
s += i.ToString();
}
Console.Write("End");
Console.Read();
}
你会发现第一个代码将很快结束,内存将在一个很好的数量。
第二个代码可能内存没问题,但会花更长的时间…更长的时间。 所以如果你有一个用户很多的应用程序,你需要速度,使用第一个。如果你有一款短期单用户应用,也许你可以同时使用两款应用,或者对开发者来说第二款应用更“自然”。
欢呼。
其他回答
StringBuilder并不总是更快:
经验法则
当连接三个或更少的动态字符串值时,使用传统的字符串连接。 当连接三个以上的动态字符串值时,请使用StringBuilder。 当从多个字符串字面值构建一个大字符串时,可以使用@字符串字面值或内联+操作符。
大多数情况下,StringBuilder是您最好的选择,但在某些情况下,如那篇文章所示,您至少应该考虑每种情况。
除了其他答案之外,请记住StringBuilder可以被告知要分配的初始内存量。
容量参数定义了当前实例分配的内存中可以存储的最大字符数。它的值被赋给Capacity属性。如果当前实例中存储的字符数量超过了这个容量值,StringBuilder对象就会分配额外的内存来存储这些字符。 如果容量为零,则使用特定于实现的默认容量。
重复添加到未预先分配的StringBuilder可能会导致大量不必要的分配,就像重复连接常规字符串一样。
If you know how long the final string will be, can trivially calculate it, or can make an educated guess about the common case (allocating too much isn't necessarily a bad thing), you should be providing this information to the constructor or the Capacity property. Especially when running performance tests to compare StringBuilder with other methods like String.Concat, which do the same thing internally. Any test you see online which doesn't include StringBuilder pre-allocation in its comparisons is wrong.
如果你无法猜测它的大小,你可能在写一个效用函数它应该有自己的可选参数来控制预分配。
. net性能专家Rico Mariani有一篇关于这个主题的文章。这并不像人们想象的那么简单。基本的建议是:
如果你的图案是这样的: X = f1(…)+ f2(…)+ f3(…)+ f4(…) 这是一个concat,它是活泼的,StringBuilder可能不会有帮助。 如果你的图案是这样的: 如果(…)x += f1(…) 如果(…)x += f2(…) 如果(…)x += f3(…) 如果(…)x += f4(…) 那么你可能需要StringBuilder。
另一篇支持这一说法的文章来自Eric Lippert,他详细描述了在一行+连接上执行的优化。
这是我为我的大规模NLP应用程序进化了十多年来最快的方法。我有IEnumerable<T>和其他输入类型的变化,有和没有不同类型的分隔符(Char, String),但在这里我展示了将数组中的所有字符串连接到单个字符串的简单情况,没有分隔符。这里的最新版本是在c# 7和。net 4.7上开发和单元测试的。
提高性能有两个关键;第一种方法是预先计算所需的确切总大小。当输入是如下所示的数组时,这一步是微不足道的。为了处理IEnumerable<T>,值得首先将字符串收集到一个临时数组中以计算总数(该数组需要避免每个元素多次调用ToString(),因为从技术上讲,考虑到副作用的可能性,这样做可能会改变'字符串连接'操作的预期语义)。
接下来,给定最终字符串的总分配大小,通过就地构建结果字符串可以获得最大的性能提升。要做到这一点,需要使用(可能有争议的)技术暂时暂停新String的不可变性,该String最初被分配为全0。然而,抛开这些争议不谈……
...请注意,这是本页上唯一的大容量连接解决方案,它完全避免了String构造函数额外的分配和复制。
完整的代码:
/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
int i;
if (rg == null || (i = rg.Length) == 0)
return String.Empty;
if (i == 1)
return rg[0];
String s, t;
int cch = 0;
do
cch += rg[--i].Length;
while (i > 0);
if (cch == 0)
return String.Empty;
i = rg.Length;
fixed (Char* _p = (s = new String(default(Char), cch)))
{
Char* pDst = _p + cch;
do
if ((t = rg[--i]).Length > 0)
fixed (Char* pSrc = t)
memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
while (pDst > _p);
}
return s;
}
[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);
我应该提到,这段代码与我自己使用的代码相比略有修改。在原文中,我从c#调用cpblk IL指令来执行实际的复制。为了代码的简单性和可移植性,我将其替换为P/Invoke memcpy,如您所见。为了在x64(但可能不是x86)上获得最高性能,您可能想要使用cpblk方法。
另一个解决方案:
在循环中,使用List而不是string。
List<string> lst= new List<string>();
for(int i=0; i<100000; i++){
...........
lst.Add(...);
}
return String.Join("", lst.ToArray());;
它非常非常快。