我的三个同事告诉我,没有理由使用StringBuilder代替使用+
运算符进行连接。换句话说,这对于一堆字符串很好:myString1 + myString2 + myString3 + myString4 + mySt...
他们使用的基本原理是,自.NET 2起,如果使用+
运算符,C#编译器将构建相同的IL,就像使用StringBuilder一样。
这对我来说是新闻。它们是否正确?
答案 0 :(得分:25)
不,他们不正确。字符串连接创建新的string
,而StringBuilder
使用可变大小的缓冲区来构建字符串,仅在调用ToString()
时创建string
对象。
如果您想进一步阅读有关该主题的内容,有很多关于字符串连接技术的讨论。大多数注重在循环中使用时不同方法的效率。在这种情况下,StringBuilder
使用concatenations of 10 or more strings的字符串运算符比字符串连接更快,这应该表明它必须使用与串联不同的方法。
也就是说,如果你连接常量字符串值,字符串运算符会更好,因为编译器会将它们分解,如果执行非循环连接,使用运算符会更好,因为它们应该导致单次拨打string.Concat
。
答案 1 :(得分:16)
不,它们不正确,它不会产生相同的IL:
static string StringBuilder()
{
var s1 = "s1";
var s2 = "s2";
var s3 = "s3";
var s4 = "s4";
var sb = new StringBuilder();
sb.Append(s1).Append(s2).Append(s3).Append(s4);
return sb.ToString();
}
static string Concat()
{
var s1 = "s1";
var s2 = "s2";
var s3 = "s3";
var s4 = "s4";
return s1 + s2 + s3 + s4;
}
String of StringBuilder:
.method private hidebysig static string StringBuilder() cil managed
{
.maxstack 2
.locals init (
[0] string s1,
[1] string s2,
[2] string s3,
[3] string s4,
[4] class [mscorlib]System.Text.StringBuilder sb)
L_0000: ldstr "s1"
L_0005: stloc.0
L_0006: ldstr "s2"
L_000b: stloc.1
L_000c: ldstr "s3"
L_0011: stloc.2
L_0012: ldstr "s4"
L_0017: stloc.3
L_0018: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_001d: stloc.s sb
L_001f: ldloc.s sb
L_0021: ldloc.0
L_0022: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0027: ldloc.1
L_0028: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_002d: ldloc.2
L_002e: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0033: ldloc.3
L_0034: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
L_0039: pop
L_003a: ldloc.s sb
L_003c: callvirt instance string [mscorlib]System.Object::ToString()
L_0041: ret
}
IL of Concat:
.method private hidebysig static string Concat() cil managed
{
.maxstack 4
.locals init (
[0] string s1,
[1] string s2,
[2] string s3,
[3] string s4)
L_0000: ldstr "s1"
L_0005: stloc.0
L_0006: ldstr "s2"
L_000b: stloc.1
L_000c: ldstr "s3"
L_0011: stloc.2
L_0012: ldstr "s4"
L_0017: stloc.3
L_0018: ldloc.0
L_0019: ldloc.1
L_001a: ldloc.2
L_001b: ldloc.3
L_001c: call string [mscorlib]System.String::Concat(string, string, string, string)
L_0021: ret
}
另外,您可能会发现this article很有趣。
答案 2 :(得分:5)
不,他们不是。他们肯定产生不同的IL。它在非String.Concat
案例中使用了不同的来电:StringBuilder
。
String.Concat
调用一个名为ConcatArray
的私有方法,该方法分配一个新字符串,足以保存最终结果。所以,非常不同,但这并不意味着使用+运算符连接的效率低于使用StringBuilder。事实上,它几乎肯定更有效率。此外,在连接常量的情况下,它在编译时完成。
但是,当您在循环中进行连接时,编译器无法执行此类优化。在这种情况下,使用StringBuilder
对于相当长的字符串会更好。
答案 3 :(得分:4)
答案是它取决于你如何连接。如果你使用带有静态字符串的+运算符,那么你的朋友是正确的 - 不需要字符串构建器。但是,如果您使用字符串变量或+ =运算符,那么您将重新分配字符串。
真正了解这里发生了什么的方法是编写一些代码然后反编译。
让我们构建一些测试代码并使用IL视图在Reflector中查看它(或者你可以使用ILDASM,无论你喜欢哪个
首先,基线 - 此方法根本不连接:
static void NoConcat()
{
string test = "Hello World";
}
现在是IL:
.method private hidebysig static void NoConcat() cil managed
{
.maxstack 1
.locals init (
[0] string test)
L_0000: nop
L_0001: ldstr "Hello World" <----------NO reallocation!
L_0006: stloc.0
L_0007: ret
}
好的,没有惊喜,对吧?
现在让我们看看一些肯定会重新分配字符串的代码,所以我们知道它是什么样的:
static void Concat2()
{
string test = "Hello";
test += " ";
test += "World";
}
这是IL,请注意重新分配(它调用string.Concat,这会导致分配一个新字符串):
.method private hidebysig static void Concat2() cil managed
{
.maxstack 2
.locals init (
[0] string test)
L_0000: nop
L_0001: ldstr "Hello"
L_0006: stloc.0
L_0007: ldloc.0
L_0008: ldstr " "
L_000d: call string [mscorlib]System.String::Concat(string, string)
L_0012: stloc.0
L_0013: ldloc.0
L_0014: ldstr "World"
L_0019: call string [mscorlib]System.String::Concat(string, string)
L_001e: stloc.0
L_001f: ret
}
好的,现在如何进行不会导致重新分配的串联 - 我们将使用“+”运算符连接静态字符串:
static void Concat1()
{
string test = "Hello" + " " + "World";
}
这是IL - 看看编译器有多聪明!它不使用concat - 它与第一个例子相同:
.method private hidebysig static void Concat1() cil managed
{
.maxstack 1
.locals init (
[0] string test)
L_0000: nop
L_0001: ldstr "Hello World"
L_0006: stloc.0
L_0007: ret
}
现在让我们玩得开心吧。如果我们混合静态字符串和变量怎么办? (这是你使用字符串制作者可能还会更好的地方)
static void Concat3(string text)
{
string test = "Hello" + " " + text + " World";
}
和IL。请注意,将“Hello”和“”组合为常量非常智能,但仍需要对文本变量进行连续处理:
.method private hidebysig static void Concat3(string text) cil managed
{
.maxstack 3
.locals init (
[0] string test)
L_0000: nop
L_0001: ldstr "Hello "
L_0006: ldarg.0
L_0007: ldstr " World"
L_000c: call string [mscorlib]System.String::Concat(string, string, string)
L_0011: stloc.0
L_0012: ret
}
答案 4 :(得分:1)
我通常遵循以下规则:
如果子字符串的数量是预先知道的,请使用连接。这涵盖了str1 + str2 + str3 + ......等情况,无论它们有多少。
如果子字符串已在数组中,请使用string.join
如果在循环中构建字符串,请使用StringBuilder
答案 5 :(得分:0)
String和StringBuilder之间略有不同:
连接字符串将创建一个新的字符串对象,该字符串对象是串联的结果。连接StringBuilder会修改字符串对象。
所以他们不正确。
答案 6 :(得分:0)
字符串连接和StringBuidler之间存在巨大的性能差异。我们的网络服务太慢了。我们将所有的字符串猫改为StringBuilder.Appends并且速度更快了!
答案 7 :(得分:0)
不,字符串连接在内部不使用StringBuilder。但是,在您的特定示例中,使用StringBuilder没有任何优势。
这适用于几个字符串(您只创建一个新字符串):
myString = myString + myString2 + myString3 + myString4 + mySt...
这不是(你正在创建和分配4个字符串等):
myString = myString + myString2;
myString = myString + myString3;
myString = myString + myString4;
myString = myString + myString5;
在关于此问题的所有stackoverflow问题中,这有一个最好的答案: String vs. StringBuilder
寻找两个答案,一个是Jay Bazuzi,另一个是James Curran。
另外,强烈建议,Jeff Atwood使用实际测试来比较字符串连接/构建的这些和其他场景,这里: http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html