连接字符串的最有效方法是什么?
.NET性能大师Rico Mariani有一篇关于这个主题的文章.它并不像人们怀疑的那么简单.基本建议如下:
如果您的模式如下:
x = f1(...) + f2(...) + f3(...) + f4(...)
这是一个concat,它很活泼,StringBuilder可能无济于事.
如果您的模式如下:
if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)
那么你可能想要StringBuilder.
另一篇支持这一主张的文章来自Eric Lippert,他在其中描述了+
以详细方式对一行连接执行的优化.
该StringBuilder.Append()
方法比使用+运算符要好得多.但我发现,当执行1000个或更少的连接时,+
效率甚至更高String.Join()
.
StringBuilder sb = new StringBuilder(); sb.Append(someString);
唯一的问题StringBuilder
是您必须使用公共分隔符连接字符串.(编辑:)正如@ryanversaw指出的那样,你可以制作分隔符string.Empty.
string key = String.Join("_", new String[] { "Customers_Contacts", customerID, database, SessionID });
有6种类型的字符串连接:
使用加号(+
)符号.
用string.Concat()
.
用string.Join()
.
用string.Format()
.
用string.Append()
.
用StringBuilder
.
在一个实验中,已经证明,string.Concat()
如果单词小于1000(大约)并且如果单词大于1000则StringBuilder
应该使用最好的方法.
有关更多信息,请查看此站点.
string.Join()vs string.Concat()
这里的string.Concat方法相当于带有空分隔符的string.Join方法调用.附加一个空字符串很快,但不这样做更快,所以string.Concat方法在这里会更好.
来自Chinh Do - StringBuilder并不总是更快:
经验法则
连接三个或更少的动态字符串值时,请使用传统的字符串连接.
连接三个以上的动态字符串值时,请使用StringBuilder.
从多个字符串文字构建一个大字符串时,请使用@ string literal或inline +运算符.
大多数时候StringBuilder是你最好的选择,但有一些案例如该帖子所示,你至少应该考虑每种情况.
如果你在一个循环中操作,StringBuilder可能是要走的路; 它可以节省您定期创建新字符串的开销.但是,在只运行一次的代码中,String.Concat可能没问题.
然而,Rico Mariani(.NET优化大师)做了一个测验,他在最后说,在大多数情况下,他推荐String.Format.
这是我用于大型NLP应用程序十多年来发展最快的方法.我有IEnumerable
和其他输入类型的变体,有和没有不同类型的分隔符(Char
,String
),但在这里我展示了将数组中的所有字符串连接成单个字符串,没有分隔符的简单情况 .这里的最新版本是在C#7和.NET 4.7上开发和单元测试的.
提高性能有两个关键因素; 首先是预先计算所需的确切总大小.当输入是如此处所示的数组时,此步骤是微不足道的.为了处理IEnumerable
代替,这是值得第一收集该总弦到临时数组计算(该阵列需要避免调用ToString()
因为技术上比每个元素一次,给出的副作用的可能性,这样做可能会改变预期的语义一个'字符串连接'操作).
接下来,给定最终字符串的总分配大小,通过就地构建结果字符串可以获得性能的最大提升.这样做需要(可能是有争议的)暂停新的不变性的技术,该新String
的最初被分配为零.除此之外,还有任何此类争议......
...请注意,这是此页面上唯一的批量连接解决方案,它完全避免了构造函数的额外轮次分配和复制
String
.
完整代码:
////// Concatenate the strings in 'rg', none of which may be null, into a single String. /// 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 代替了,正如你所看到的.为了在x64上获得最高性能(但可能不是x86),您可能需要使用cpblk方法.memcpy
从这篇MSDN文章:
在时间和内存中创建StringBuilder对象都会产生一些开销.在具有快速内存的机器上,如果您正在执行大约五个操作,则StringBuilder变得值得.根据经验,我会说10个或更多字符串操作是任何机器上的开销的理由,即使是较慢的.
因此,如果您信任MSDN,请使用StringBuilder,如果您必须执行超过10个字符串操作/连接 - 否则使用'+'的简单字符串连接就可以了.
添加到其他答案,请记住,StringBuilder可以被告知要分配的初始内存量.
所述容量参数定义可被存储在由当前实例所分配的存储器的字符的最大数目.其值分配给Capacity属性.如果要存储在当前实例中的字符数超过此容量值,则StringBuilder对象会分配额外的内存来存储它们.
如果capacity为零,则使用特定于实现的默认容量.
重复附加到尚未预先分配的StringBuilder可能会导致大量不必要的分配,就像重复连接常规字符串一样.
如果您知道最后一个字符串将持续多长时间,可以简单地计算它,或者可以对常见情况进行有根据的猜测(分配太多不一定是坏事),您应该将这些信息提供给构造函数或者容量财产.特别是在运行性能测试时,将StringBuilder与其他方法(如String.Concat)进行比较,这些方法在内部执行相同的操作.您在网上看到的任何测试都没有包含StringBuilder在其比较中的预分配是错误的.
如果你不能对大小做任何猜测,你可能正在编写一个实用函数,它应该有自己的可选参数来控制预分配.