[英]Does string concatenation use StringBuilder internally?
我的三個同事告訴我,沒有理由使用StringBuilder代替使用+
運算符進行連接。 換句話說,這對一堆字符串很好: myString1 + myString2 + myString3 + myString4 + mySt...
他們使用的基本原理是,從.NET 2開始,如果使用+
運算符,C#編譯器將構建相同的IL,就像使用StringBuilder一樣。
這對我來說是新聞。 他們是對的嗎?
不,他們不正確。 字符串連接創建一個新string
而StringBuilder
使用可變大小的緩沖區來構建字符串,只在調用ToString()
時創建一個string
對象。
如果您想進一步閱讀有關該主題的內容,那么在互聯網上有很多關於字符串連接技術的討論。 大多數注重在循環中使用時不同方法的效率。 在這種情況下, StringBuilder
使用字符串運算符進行字符串連接比10個或更多字符串的連接更快,這應該表明它必須使用與連接不同的方法。
也就是說,如果你連接常量字符串值,字符串運算符會更好,因為編譯器會將它們分解,如果執行非循環連接,使用運算符會更好,因為它們應該導致單個調用string.Concat
。
不,他們不正確,它不會產生相同的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;
}
IL的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
}
你也可能會覺得這篇文章很有趣。
不,他們不是。 他們肯定產生不同的IL。 它使用不同的調用:非StringBuilder
情況下的String.Concat
。
String.Concat
調用一個名為ConcatArray
的私有方法,該方法分配一個新字符串,其長度足以保存最終結果。 所以,非常不同,但這並不意味着使用+運算符連接的效率低於使用StringBuilder。 事實上,它幾乎肯定更有效率。 此外,在連接常量的情況下,它在編譯時完成。
但是,當您在循環中進行連接時,編譯器無法執行此類優化。 在這種情況下,使用StringBuilder
對於相當長的字符串會更好。
答案是它取決於你如何連接。 如果你使用帶有靜態字符串的+運算符,那么你的朋友是正確的 - 不需要字符串構建器。 但是,如果使用字符串變量或+ =運算符,則需要重新分配字符串。
真正了解這里發生了什么的方法是編寫一些代碼然后反編譯。
讓我們構建一些測試代碼並使用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
}
現在讓我們玩得開心吧。 如果我們混合靜態字符串和變量怎么辦? (這是你使用stringbuilder可能還會更好的地方)
static void Concat3(string text)
{
string test = "Hello" + " " + text + " World";
}
和IL。 請注意,將“Hello”和“”組合為常量非常智能,但仍需要為文本變量執行concat:
.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
}
我通常遵循以下規則:
如果子字符串的數量是預先知道的,請使用連接。 這涵蓋了像str1 + str2 + str3 + ......這樣的情況,無論它們有多少。
如果子字符串已在數組中,請使用string.join
如果在循環中構建字符串,請使用StringBuilder
String和StringBuilder之間略有不同:
連接String將創建一個新的字符串對象,它是串聯的結果。 連接StringBuilder會修改字符串對象。
所以他們不正確。
字符串連接和StringBuidler之間存在巨大的性能差異。 我們的網絡服務太慢了。 我們將所有的串貓改為StringBuilder.Appends並且速度更快了!
不,字符串連接在內部不使用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-優化-theater.html
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.