最有效的方法来连接字符串?
连接字符串的最有效的方法是什么?
StringBuilder.Append()
方法比使用+运算符好得多。 但是我发现,当执行1000个或更少的连接时, String.Join()
比StringBuilder
更有效率。
StringBuilder sb = new StringBuilder(); sb.Append(someString);
String.Join
唯一的问题是你必须用一个公共的分隔符连接字符串。 (编辑:)如@ryanversaw指出,你可以使分隔字符串.Empty。
string key = String.Join("_", new String[] { "Customers_Contacts", customerID, database, SessionID });
.NET Performance guru Rico Mariani就这个问题写了一篇文章 。 这并不像人们怀疑的那么简单。 基本的建议是这样的:
如果你的模式看起来像:
x = f1(...) + f2(...) + f3(...) + f4(...)
这是一个concat,它是zippy,StringBuilder可能不会帮助。
如果你的模式看起来像:
if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)
那么你可能需要StringBuilder。
另一篇支持这一说法的文章来自Eric Lippert,他在那里详细描述了在一行+
连接上进行的优化。
有6种类型的字符串连接:
- 使用加号(
+
)符号。 - 使用
string.Concat()
。 - 使用
string.Join()
。 - 使用
string.Format()
。 - 使用
string.Append()
。 - 使用
StringBuilder
。
在一个实验中,已经证明了如果单词少于1000(大约), string.Concat()
是最好的方法,如果单词大于1000,那么应该使用StringBuilder
。
欲了解更多信息,请查看本网站 。
string.Join()vs string.Concat()
这里的string.Concat方法等价于使用空分隔符的string.Join方法调用。 附加一个空字符串是快速的,但不这样做更快,所以string.Concat方法在这里会更好。
来自Chinh – StringBuilder并不总是更快 :
经验法则
-
连接三个动态字符串值或更少时,使用传统字符串连接。
-
当连接三个以上的动态字符串值时,使用StringBuilder。
-
从几个字符串文字构建一个大字符串时,请使用@字符串文字或内联运算符。
大部分时间StringBuilder是你最好的选择,但是在这个帖子中有一些情况应该至少考虑一下每个情况。
如果你在循环操作,StringBuilder可能是要走的路; 它可以节省您定期创建新字符串的开销。 在只能运行一次的代码中,String.Concat可能没问题。
然而,Rico Mariani(.NET优化大师) 做了一个测验 ,他在最后说道,在大多数情况下,他推荐使用String.Format。
从这个MSDN文章 :
在创建一个StringBuilder对象的时候和内存都有一些开销。 在具有快速内存的机器上,如果您正在执行五个操作,那么一个StringBuilder就会变得有价值。 作为一个经验法则,我会说10个或更多的字符串操作是在任何机器上开销的理由,即使是较慢的。
所以,如果你信任MSDN去与StringBuilder,如果你必须做超过10个字符串操作/连接 – 否则简单的字符串连字符与'+'是好的。
添加到其他答案,请记住, 可以告诉StringBuilder分配的初始内存量 。
容量参数定义当前实例分配的内存中可以存储的最大字符数。 它的值被分配给Capacity属性。 如果当前实例中要存储的字符数超过此容量值,则StringBuilder对象会分配额外的内存来存储它们。
如果容量为零,则使用实现特定的默认容量。
反复附加到未预先分配的StringBuilder可能会导致大量不必要的分配,就像重复连接常规字符串一样。
如果你知道最后一个字符串会有多长,可以简单地计算出来,或者可以对普通情况进行有根据的猜测(分配太多不一定是坏事),你应该把这个信息提供给构造函数或者容量属性。 特别是在运行性能测试来比较StringBuilder与其他方法如String.Concat,它们在内部执行相同的操作。 你在网上看到的任何不包含StringBuilder预分配的测试都是错误的。
如果你不能猜测这个大小,你可能正在编写一个实用程序函数,它应该有自己的可选参数来控制预分配。
指出这一点也很重要,如果串联字符串文字 ,应该使用+
运算符。
当通过使用+运算符连接字符串文字或字符串常量时,编译器会创建一个字符串。 没有运行时串联发生。
如何连接多个字符串(C#编程指南)
最有效的是使用StringBuilder,就像这样:
StringBuilder sb = new StringBuilder(); sb.Append("string1"); sb.Append("string2"); ...etc... String strResult = sb.ToString();
@jonezy:String.Concat是好的,如果你有一些小事情。 但是,如果你连接了兆字节的数据,你的程序可能会消失。
这真的取决于你的使用模式。 string.Join,string,Concat和string.Format之间的详细基准可以在这里找到: String.Format不适用于强化日志记录
(这实际上是我给这个问题的同样的答案)
System.String是不可变的。 当我们修改一个字符串变量的值时,一个新的内存被分配给新的值,并释放先前的内存分配。 System.StringBuilder被设计为具有可变字符串的概念,其中可以执行各种操作,而不用为修改的字符串分配单独的存储器位置。
试试这两个代码,你会发现解决方案。
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(); }
你会发现第一个代码将会非常快速地结束,并且内存将会很大。
第二个代码也许内存会好的,但是会花费更长的时间。 所以,如果你有很多用户的应用程序,并且需要速度,那就使用1st。 如果你有一个短期的一个用户应用程序的应用程序,也许你可以使用两个或第二个将更加“自然”的开发人员。
干杯。
以下可能是多个替代解决方案来连接多个字符串。
String str1 = "sometext"; string str2 = "some other text"; string afterConcate = $"{str1}{str2}";
字符串插值
对于只有两个字符串,你绝对不想使用StringBuilder。 有一些阈值高于StringBuilder的开销小于分配多个字符串的开销。
所以,对于更多的2-3个字符串,使用DannySmurf的代码 。 否则,只需使用+运算符。
这将取决于代码。 StringBuilder一般来说效率更高,但是如果你只连接了几个字符串并在一行中完成,那么代码优化可能会为你处理。 思考代码的外观也很重要:对于较大的设置,StringBuilder会使读起来更容易,对于小的StringBuilder只会增加不必要的混乱。
这是我十多年来为我的大型NLP应用程序演变而来的最快速的方法。 我有IEnumerable<T>
和其他输入类型的变体,有和没有不同类型( Char
, String
)的分隔符,但在这里我展示了一个简单的例子, 将数组中的所有字符串连接成一个单独的字符串,没有分隔符。 这里的最新版本是在C#7和.NET 4.7上开发和单元测试的。
有两个关键要更高的性能; 首先是预先计算所需的确切总大小。 当输入是一个数组时,这一步是微不足道的,如下所示。 为了处理IEnumerable<T>
相反,它是值得首先收集的字符串到一个临时数组计算total(该数组需要避免每个元素多次调用ToString()
从技术上说,考虑到副作用的可能性,这样做可能会改变“字符串连接”操作的预期语义)。
接下来,给定最终字符串的总分配大小,通过就地生成结果字符串来获得最大的性能提升。 这样做需要暂时中止一个新的String
的不可变性的(也许是有争议的)技术,该String
最初被分配了满零。 抛开任何这样的争议,但是…
…请注意,这是这个页面上唯一的批量连接解决方案,完全避免了由
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方法。