StringBuilder ne zaman kullanılır?


83

StringBuilder'ın faydalarını anlıyorum.

Ama 2 dizgeyi birleştirmek istersem, bunu StringBuilder olmadan yapmanın daha iyi (daha hızlı) olduğunu varsayıyorum. Bu doğru mu?

StringBuilder'ı kullanmak hangi noktada (dizi sayısı) daha iyi hale gelir?


1
Bunun daha önce ele alındığına inanıyorum.
Mark Schultheiss


Yanıtlar:


79

Jeff Atwood'un The Sad Tragedy of Micro-Optimization Theater'ı okumanızı şiddetle tavsiye ederim .

Basit Birleştirme ile StringBuilder'ı diğer yöntemlerle karşılaştırır.

Şimdi, bazı sayılar ve grafikler görmek istiyorsanız, bağlantıyı takip edin;)


Evet için +1 ! Bunun için endişelenmek için harcanan zaman, gerçekten önemli olabilecek bir şeyi yapmadan harcanan zamandır.
Greg D

8
Ancak okumanız yanlış: Çoğu durumda, döngü olmadığında önemli değil, diğer durumlarda çok önemli olabilir
Peter

1
Kabul edilen bir cevapta sadece yanlış bilgi olduğu için düzenlemeyi kaldırdım.
Peter

2
ve atıfta bulunduğunuz makaleden ne kadar önemli olduğunu göstermek için: "Çöp toplanan dillerin çoğunda dizeler değişmezdir: iki dize eklediğinizde, her ikisinin içeriği de kopyalanır. Bu döngüde sonuca eklemeye devam ederken , her seferinde daha fazla bellek ayrılıyor. Bu, doğrudan korkunç dörtlü n2 performansına yol açar "
Peter

2
Bu neden kabul edilen cevaptır. Sadece bir bağlantıyı bırakıp "git bunu oku" demenin iyi bir cevap olduğunu düşünmüyorum
Kolob Canyon

45

Ancak 2 dizgeyi birleştirmek istersem, bunu StringBuilder olmadan yapmanın daha iyi (daha hızlı) olduğunu varsayıyorum. Bu doğru mu?

Bu gerçekten doğru, neden tam olarak açıklandığını şurada bulabilirsiniz:

http://www.yoda.arachsys.com/csharp/stringbuilder.html

Özetle: dizeleri tek seferde birleştirebiliyorsanız

var result = a + " " + b  + " " + c + ..

StringBuilder olmadan sadece kopyalama yapıldığında daha iyi durumda olursunuz (ortaya çıkan dizgenin uzunluğu önceden hesaplanır.);

Gibi yapı için

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

her seferinde yeni nesneler oluşturulur, bu nedenle burada StringBuilder'ı düşünmelisiniz.

Sonunda makale şu temel kuralları özetliyor:

Başparmak Kuralları

Peki, StringBuilder'ı ne zaman kullanmalısınız ve dize birleştirme operatörlerini ne zaman kullanmalısınız?

  • Önemsiz olmayan bir döngüde birleştirirken kesinlikle StringBuilder kullanın - özellikle (derleme zamanında) döngü boyunca kaç yineleme yapacağınızı kesin olarak bilmiyorsanız. Örneğin, bir dosyayı her seferinde bir karakter okumak, devam ederken + = operatörünü kullanarak bir dize oluşturmak potansiyel olarak performans intiharıdır.

  • Tek bir ifadede birleştirilmesi gereken her şeyi (okunabilir bir şekilde) belirleyebildiğinizde, kesinlikle birleştirme operatörünü kullanın. (Birleştirilecek bir dizi şeyiniz varsa, String.Concat'i açıkça çağırmayı veya bir sınırlayıcıya ihtiyacınız varsa String.Join'i çağırmayı düşünün.)

  • Değişmez değerleri birkaç bitiştirilmiş bitlere bölmekten korkmayın - sonuç aynı olacaktır. Uzun bir harfi birkaç satıra bölerek okunabilirliğe yardımcı olabilirsiniz, örneğin, performansa zarar vermeden.

  • Birleştirme işleminin bir sonraki yinelemesini beslemekten başka bir şey için ara sonuçlara ihtiyacınız varsa, StringBuilder size yardımcı olmayacaktır. Örneğin, bir ad ve soyaddan tam bir ad oluşturursanız ve ardından üçüncü bir bilgi parçasını (takma ad, belki) eklerseniz, StringBuilder kullanmaktan yalnızca bunu yapmazsanız yararlanacaksınız. (ad + soyadı) dizesine başka bir amaçla ihtiyaç duyuyoruz (bir Person nesnesi oluşturan örnekte yaptığımız gibi).

  • Yapmanız gereken birkaç bitiştirmeniz varsa ve bunları gerçekten ayrı ifadeler halinde yapmak istiyorsanız, hangi yöne gittiğiniz önemli değildir. Hangi yolun daha verimli olduğu, birleştirme sayısına, ilgili dizenin boyutlarına ve hangi sırayla birleştirildiklerine bağlı olacaktır. Bu kod parçasının performans darboğazı olduğuna gerçekten inanıyorsanız, her iki şekilde de profil veya kıyaslama yapın.


14

System.String değişmez bir nesnedir - bu, içeriğini her değiştirdiğinizde yeni bir dizge tahsis edeceği ve bunun zaman (ve bellek?) Alacağı anlamına gelir. StringBuilder'ı kullanarak, yeni bir tane ayırmadan nesnenin gerçek içeriğini değiştirirsiniz.

Bu nedenle, dizede birçok değişiklik yapmanız gerektiğinde StringBuilder'ı kullanın.


8

Pek değil ... Eğer büyük dizgeleri birleştirirseniz veya bir döngüde olduğu gibi çok sayıda birleştirmeniz varsa StringBuilder'ı kullanmalısınız .


1
Bu yanlış. Sen kullanmalıdır StringBuilderdöngü veya birleştirme özellikleri bir performans sorunu ise.
Alex Bagnolini 01

2
@Alex: Bu her zaman böyle değil mi? ;) Hayır, cidden, StringBuilder'ı bir döngü içinde birleştirme için her zaman kullandım ... yine de, döngülerimin hepsinde 1k'den fazla yineleme var ... @Binary: Normalde, bu derlenmeli string s = "abcd", en azından bu son şey Duydum ki ... değişkenler söz konusu olduğunda büyük ihtimalle Concat olurdu.
Bobby

1
Gerçek şu ki: neredeyse HER ZAMAN durum böyle DEĞİLDİR. Her zaman string operatörleri kullandım a + "hello" + "somethingelse"ve hiç endişelenmedim. Sorun olacaksa, StringBuilder'ı kullanacağım. Ama ilk başta endişelenmedim ve yazmaya daha az zaman harcadım.
Alex Bagnolini

3
Büyük dizelerde kesinlikle hiçbir performans avantajı yoktur - yalnızca birçok birleştirme ile.
Konrad Rudolph

1
@Konrad: Performans avantajı olmadığından emin misiniz ? Büyük dizeleri her birleştirdiğinizde, büyük miktarda veri kopyalıyorsunuz; Küçük dizeleri her birleştirdiğinizde, yalnızca az miktarda veri kopyalıyorsunuz.
LukeH

6
  • Bir döngüde dizeleri birleştirirseniz, normal String yerine StringBuilder kullanmayı düşünmelisiniz.
  • Tek bir birleştirme olması durumunda, yürütme süresindeki farkı hiç göremeyebilirsiniz.

İşte konuyu kanıtlamak için basit bir test uygulaması:

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

Sonuçlar:

  • 30'000 yineleme

    • Dize - 2000 ms
    • StringBuilder - 4 ms
  • 1000 yineleme

    • Dize - 2 ms
    • StringBuilder - 1 ms
  • 500 yineleme

    • Dize - 0 ms
    • StringBuilder - 0 ms

5

Kelimeleri ifade etmek

O halde üçe kadar sayacaksın, ne fazla ne az. Sayacağınız sayı üç olacak ve sayım sayısı üç olacaktır. Dördü saymayacaksın, ikisini de saymayacaksın, üçe geçmen dışında. Üçüncü sayı olan üç numaraya ulaşıldığında, Antakya'nın Kutsal El Bombasını atarsın.

Genellikle üç veya daha fazla dizginin birleştirilmesine neden olacak herhangi bir kod bloğu için dize oluşturucuyu kullanırım.


Şuna bağlıdır: Concetanation yalnızca bir kopya oluşturur: "Russell" + "" + Steen + ".", Dizenin uzunluğunu önceden hesapladığı için yalnızca bir kopya oluşturur. Yalnızca birleştirme işleminizi bölmeniz gerektiğinde, bir oluşturucu hakkında düşünmeye başlamalısınız
Peter

4

Kesin bir cevap yok, sadece pratik kurallar. Benim kişisel kurallarım şuna benzer:

  • Bir döngüde bitiştiriyorsanız, her zaman bir StringBuilder.
  • Dizeler büyükse, her zaman bir StringBuilder.
  • Birleştirme kodu derli toplu ve ekranda okunabilir durumdaysa muhtemelen sorun yoktur.
    Değilse, bir StringBuilder.

Bunun eski bir konu olduğunu biliyorum, ancak sadece öğrenmeyi biliyorum ve "Büyük dizi" olarak düşündüğünüz şeyi bilmek istiyorum.
MatthewD

4

Ama 2 dizgeyi birleştirmek istersem, bunu StringBuilder olmadan yapmanın daha iyi ve daha hızlı olduğunu varsayıyorum. Bu doğru mu?

Evet. Ancak daha da önemlisi, bu tür durumlarda vanilya kullanmak çok daha okunaklıString . Öte yandan, bir döngüde kullanmak mantıklı ve aynı zamanda birleştirme kadar okunabilir de olabilir.

Belirli sayıda birleştirme işlemini bir eşik olarak belirten genel kurallara karşı dikkatli olurdum. Bunu döngülerde (ve yalnızca döngülerde) kullanmak muhtemelen aynı derecede yararlıdır, hatırlaması daha kolaydır ve daha mantıklıdır.


"Belirli sayıda birleştirme işlemini bir eşik olarak belirten genel kurallara karşı dikkatli olurum" <bu. Ayrıca, sağduyu uygulandıktan sonra, 6 ay sonra kodunuza geri gelecek kişiyi düşünün.
Phil Cooper

4

Bunun için fikirlerden etkilenmeyen veya bir gurur savaşı izlemeyen bir açıklama bulmak zor olduğundan, bunu kendim test etmek için LINQpad'e biraz kod yazmayı düşündüm.

İ.ToString () kullanmak yerine küçük boyutlu dizeler kullanmanın yanıt sürelerini değiştirdiğini buldum (küçük döngülerde görülebilir).

Test, zaman ölçümlerini makul şekilde karşılaştırılabilir aralıklarda tutmak için farklı yineleme dizileri kullanır.

En sonunda kodu kopyalayacağım, böylece kendiniz deneyebilirsiniz (results.Charts ... Dump () LINQPad dışında çalışmaz).

Çıktı (X Ekseni: Test edilen yineleme sayısı, Y Ekseni: Onaylarla alınan süre):

Yineleme sırası: 2, 3, 4, 5, 6, 7, 8, 9, 10 Yineleme sırası: 2, 3, 4, 5, 6, 7, 8, 9, 10

Yineleme sırası: 10, 20, 30, 40, 50, 60, 70, 80 Yineleme sırası: 10, 20, 30, 40, 50, 60, 70, 80

Yineleme sırası: 100, 200, 300, 400, 500 Yineleme sırası: 100, 200, 300, 400, 500

Kod (LINQPad 5 kullanılarak yazılmıştır):

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();

    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();

    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 

    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;

        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }

        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }

        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;

    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);

        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }

    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}

3

Birleştirme sayısını fiziksel olarak yazabildiğiniz sürece (a + b + c ...) bu büyük bir fark yaratmamalıdır. N kare (N = 10'da) 100X yavaşlamadır ve bu çok da kötü olmamalıdır.

Büyük sorun, yüzlerce dizeyi birleştirdiğiniz zamandır. N = 100'de 10000X kat yavaşlama elde edersiniz. Bu oldukça kötü.


2

Ne zaman kullanıp ne zaman kullanmamalı arasında ince bir çizgi olduğunu sanmıyorum. Tabii birisi altın koşullarla ortaya çıkmak için kapsamlı testler yapmadıysa.

Benim için, sadece 2 büyük dizgeyi birleştirirseniz StringBuilder kullanmayacağım. Belirsiz sayıma sahip bir döngü varsa, döngü küçük sayımlar olsa bile, muhtemelen yapacağım.


Aslında, StringBuilder'ı 2 dizgeyi birleştirmek için kullanmak tamamen yanlış olur, ancak bunun perf ile ilgisi yoktur. test - bu sadece onu yanlış şey için kullanmaktır.
Marc Gravell

1

Tek bir birleştirme bir StringBuilder kullanmaya değmez. Genel bir kural olarak genellikle 5 birleştirme kullandım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.