簡體   English   中英

為什么StringBuilder比字符串連接慢?

[英]Why is StringBuilder slower than string concatenation?

與+串聯相比,為什么StringBuilder變慢? StringBuilder旨在避免額外的對象創建,但為什么它會懲罰性能呢?

    static void Main(string[] args)
    {
        int max = 1000000;
        for (int times = 0; times < 5; times++)
        {
            Console.WriteLine("\ntime: {0}", (times+1).ToString());
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < max; i++)
            {
                string msg = "Your total is ";
                msg += "$500 ";
                msg += DateTime.Now;
            }
            sw.Stop();
            Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

            sw = Stopwatch.StartNew();
            for (int j = 0; j < max; j++)
            {
                StringBuilder msg = new StringBuilder();
                msg.Append("Your total is ");
                msg.Append("$500 ");
                msg.Append(DateTime.Now);
            }
            sw.Stop();
            Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
        }
        Console.Read();
    }

在此輸入圖像描述

編輯:按建議移出范圍變量:

在此輸入圖像描述

更改以便不會一直實例化StringBuilder,而是.Clear()它:

time: 1
String +    :   3348ms
StringBuilder   :   3151ms

time: 2
String +    :   3346ms
StringBuilder   :   3050ms

等等

請注意 ,這仍然測試完全相同的功能,但嘗試更智能地重用資源。

代碼:(也訪問http://ideone.com/YuaqY

using System;
using System.Text;
using System.Diagnostics;

public class Program
{
    static void Main(string[] args)
    {
        int max = 1000000;
        for (int times = 0; times < 5; times++)
        {
            {
                Console.WriteLine("\ntime: {0}", (times+1).ToString());
                Stopwatch sw = Stopwatch.StartNew();
                for (int i = 0; i < max; i++)
                {
                    string msg = "Your total is ";
                    msg += "$500 ";
                    msg += DateTime.Now;
                }
                sw.Stop();
                Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }

            {
                Stopwatch sw = Stopwatch.StartNew();
                StringBuilder msg = new StringBuilder();
                for (int j = 0; j < max; j++)
                {
                    msg.Clear();
                    msg.Append("Your total is ");
                    msg.Append("$500 ");
                    msg.Append(DateTime.Now);
                }
                sw.Stop();
                Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }
        }
        Console.Read();
    }
}

您正在每次迭代時創建一個StringBuilder的新實例,這會產生一些開銷。 既然你沒有將它用於它實際意圖做的事情(即:構建大字符串,否則需要許多字符串連接操作),看到比串聯更糟糕的性能並不奇怪。

StringBuilder更常見的比較/用法如下:

string msg = "";
for (int i = 0; i < max; i++)
{
    msg += "Your total is ";
    msg += "$500 ";
    msg += DateTime.Now;
}

StringBuilder msg_sb = new StringBuilder();
for (int j = 0; j < max; j++)
{
    msg_sb.Append("Your total is ");
    msg_sb.Append("$500 ");
    msg_sb.Append(DateTime.Now);
}

有了這個,你會發現StringBuilder和串聯之間存在顯着的性能差異。 而“重要”是指數量級 ,而不是您在示例中觀察到的~10%的差異。

由於StringBuilder不需要構建大量的中間字符串而不會被丟棄,因此可以獲得更好的性能。 這就是它的意思。 對於較小的字符串,最好使用字符串連接以簡化和清晰。

使用更長的字符串時,StringBuilder的好處應該是顯而易見的。

每次連接字符串時都會創建一個新的字符串對象,因此字符串越長,從舊字符串復制到新字符串所需的越多。

此外,創建許多臨時對象可能會對StopWatch無法測量的性能產生負面影響,因為它會使用臨時對象“污染”托管堆,並可能導致更多垃圾回收周期。

修改你的測試以創建(更多)更長的字符串並使用(更多)更多的連接/追加操作,並且StringBuilder應該表現更好。

除了不以最有效的方式使用StringBuilder之外,您還沒有盡可能有效地使用字符串連接。 如果你知道你提前連接了多少個字符串,那么在一行上完成這一切應該是最快的。 編譯器優化操作,以便不生成任何中間字符串。

我添加了幾個測試用例。 一個與sehe建議的基本相同,另一個在一行中生成字符串:

sw = Stopwatch.StartNew();
builder = new StringBuilder();
for (int j = 0; j < max; j++)
{
    builder.Clear();
    builder.Append("Your total is ");
    builder.Append("$500 ");
    builder.Append(DateTime.Now);
}
sw.Stop();
Console.WriteLine("StringBuilder (clearing)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

sw = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
    msg = "Your total is " + "$500" + DateTime.Now;
}
sw.Stop();
Console.WriteLine("String + (one line)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

以下是我在機器上看到的輸出示例:

time: 1
String +    :   3707ms
StringBuilder   :   3910ms
StringBuilder (clearing)    :   3683ms
String + (one line) :   3645ms

time: 2
String +    :   3703ms
StringBuilder   :   3926ms
StringBuilder (clearing)    :   3666ms
String + (one line) :   3625ms

一般來說: - 如果你在很多步驟中構建一個大字符串,或者你不知道將多少個字符串連接在一起, StringBuilder會做得更好。
- 只要它是一個合理的選項選項,將它們全部混合在一個表達式中就更好了。

注意

string msg = "Your total is ";
msg += "$500 ";
msg += DateTime.Now;

匯編到

string msg = String.Concat("Your total is ", "$500 ");
msg = String.Concat(msg, DateTime.Now.ToString());

每次迭代總計兩個concats和一個ToString。 此外,單個String.Concat非常快,因為它知道結果字符串的大小,因此它只分配結果字符串一次,然后快速將源字符串復制到其中。 這意味着在實踐中

String.Concat(x, y);

永遠都會超越

StringBuilder builder = new StringBuilder();
builder.Append(x);
builder.Append(y);

因為StringBuilder不能使用這樣的快捷方式(你可以調用一個附加的,或者一個刪除,這是String.Concat無法實現的)。

StringBuilder的工作方式是分配一個初始緩沖區並將字符串長度設置為0.對於每個Append,它必須檢查緩沖區,可能分配更多緩沖區空間(通常將舊緩沖區復制到新緩沖區),復制字符串並增加構建器的字符串長度。 String.Concat不需要做所有這些額外的工作。

因此,對於簡單的字符串連接,x + y(即String.Concat)將始終優於StringBuilder。

現在,一旦開始將大量字符串連接到單個緩沖區中,或者你在緩沖區上進行大量操作,你就會開始從StringBuilder中獲益,在不使用StringBuilder時你需要繼續創建新的字符串。 。 這是因為StringBuilder只是偶爾以塊的形式分配新的內存,但String.Concat,String.SubString等(幾乎)總是分配新的內存。 (像“”.SubString(0,0)或String.Concat(“”,“”)之類的東西不會分配內存,但這些都是退化的情況。)

我認為比較String和StringBuilder之間的效率而不是時間更好。

msdn說:String被稱為不可變的,因為它的值一旦創建就無法修改。 看似修改String的方法實際上返回一個包含修改的新String。 如果需要修改類似字符串的對象的實際內容,請使用System.Text.StringBuilder類。

string msg = "Your total is "; // a new string object
msg += "$500 "; // a new string object
msg += DateTime.Now; // a new string object

看哪一個更好。

下面是一個示例,演示了StringBuilder執行速度比字符串連接更快的情況:

static void Main(string[] args)
{
    const int sLen = 30, Loops = 10000;
    DateTime sTime, eTime;
    int i;
    string sSource = new String('X', sLen);
    string sDest = "";
    // 
    // Time StringBuilder.
    // 
    for (int times = 0; times < 5; times++)
    {
        sTime = DateTime.Now;
        System.Text.StringBuilder sb = new System.Text.StringBuilder((int)(sLen * Loops * 1.1));
        Console.WriteLine("Result # " + (times + 1).ToString());
        for (i = 0; i < Loops; i++)
        {
            sb.Append(sSource);
        }
        sDest = sb.ToString();
        eTime = DateTime.Now;
        Console.WriteLine("String Builder took :" + (eTime - sTime).TotalSeconds + " seconds.");
        // 
        // Time string concatenation.
        // 
        sTime = DateTime.Now;
        for (i = 0; i < Loops; i++)
        {
            sDest += sSource;
            //Console.WriteLine(i);
        }
        eTime = DateTime.Now;
        Console.WriteLine("Concatenation took : " + (eTime - sTime).TotalSeconds + " seconds.");
        Console.WriteLine("\n");
    }
    // 
    // Make the console window stay open
    // so that you can see the results when running from the IDE.
    // 
}

結果#1 String Builder占用:0秒。 連接采取:8.7659616秒。

結果#2 String Builder占用:0秒。 連接采取:8.7659616秒。

結果#3 String Builder占用:0秒。 連接采取:8.9378432秒。

結果#4 String Builder占用:0秒。 連接采取:8.7972128秒。

結果#5 String Builder占用:0秒。 連接采取:8.8753408秒。

StringBulder比+串聯快得多..

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM