Menu

C# StringBuilder: Append, Insert, Replace ve Ne Zaman Kullanılır

C#'ta bir döngüde tekrar tekrar string birleştirmenin neden yavaş olduğu ve StringBuilder'ın bunu nasıl düzelttiği: Append, AppendLine, AppendFormat, Insert, Remove, Replace, kapasite ve düz birleştirmenin ya da string.Join'in ne zaman daha iyi seçim olduğu.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

C#'ta string'ler değiştirilemez: s += "x" s'yi uzatmaz, eski karakterleri artı yenilerini içeren yeni bir string yaratır ve eski string çöp olur. Bir kez olması hiçbir şeydir. Binlerce kez dönen bir döngüde ise her yineleme o ana kadar oluşturulan her şeyi kopyalar, bu yüzden toplam iş uzunluğun karesiyle büyür.

System.Text içindeki StringBuilder değiştirilebilir bir tampondur. Append sondaki boş alana yazar, tamponu ara sıra büyütür ve ToString() en sonda tek bir string oluşturur.

Çıktı:

Order #1042, total 59.97
Status: shipped
**********
51

Döngüde birleştirme neden yavaş

İşte aynı metin iki yolla oluşturuluyor. Süreler makineden makineye değişir, bu yüzden aşağıdaki sayılar yalnızca bir örnektir; önemli olan aradaki farktır ve döngü büyüdükçe açılır:

Örnek çıktı:

string +=     482 ms, length 188890
StringBuilder 1 ms, length 188890

20.000 eklemeyle += sürümü toplamda neredeyse 2 milyar karakter kopyalar. StringBuilder her karakteri yaklaşık iki kez kopyalar: eklendiğinde bir kez ve ToString()'de bir kez daha; bir milisaniye civarında kalmasının nedeni budur.

Append, AppendLine ve AppendFormat

Append'in her yerleşik tip için overload'u vardır ve aynı builder'ı döndürür, bu yüzden çağrılar zincirlenebilir. AppendLine argümanından sonra bir satır sonu ekler. AppendFormat, string.Format gibi bileşik bir biçim string'i alır:

Çıktı:

RECEIPT
-------
Espresso        2.40
Croissant       3.10
Orange juice    4.50
TOTAL          10.00

Interpolated bir string eklemek (sb.Append($"...")) sorun değildir ve iyi okunur. .NET 6 ve sonrasında derleyici ara string'i oluşturmadan parçaları doğrudan builder'a bile yazar.

AppendLine Environment.NewLine'ı kullanır: Linux ve macOS'ta \n, Windows'ta \r\n. Bir dosya biçimi belirli bir satır sonu gerektiriyorsa onu kendiniz ekleyin.

Insert, Remove, Replace ve indeksleyici

Bir string'in aksine bir StringBuilder yerinde düzenlenebilir:

Çıktı:

Hello, World
Hello, Maya
Maya
maya
maya!
[] length 0

Length'i daha küçük bir değere ayarlamak kırpar; sondaki bir ayırıcıyı kaldırmanın en ucuz yoludur. Clear() uzunluğu 0 yapar ve ayrılmış tamponu korur, böylece tek bir builder dış bir döngünün yinelemelerinde yeniden kullanılabilir.

İndeksleyici son eklemeler için hızlıdır, ama çok büyük bir builder'da rastgele erişimde yavaş olabilir, çünkü içerik bağlı parçalar halinde saklanır. Karakterleri okumanız gerekiyorsa bir kez ToString() çağırın ve string'i indeksleyin.

Sondaki ayırıcı sorunu

Klasik bir kullanım, virgülle ayrılmış bir liste oluşturmaktır. Her öğeden sonra ayırıcı eklemek sonda bir fazladan bırakır:

Çıktı:

csharp, dotnet, linq
csharp, dotnet, linq

Öğeler zaten bir koleksiyondaysa string.Join daha kısadır ve en az onun kadar hızlıdır. StringBuilder'ı şekli mantığa bağlı olan metin için saklayın: koşullu bölümler, iç içe döngüler, karışık biçimlendirme.

Kapasite

Bir StringBuilder 16 karakterlik yerle başlar. Yer bittiğinde, zaten tuttuğu her şey kadar büyük (blok başına en fazla 8.000 karakter) yeni bir depolama bloğu bağlar; böylece küçükken kapasitesi ikiye katlanır, sonra 8.000 karakterlik adımlarla büyür. Büyümek, zaten saklanan metni asla kopyalamaz. Sonucun yaklaşık ne kadar uzun olacağını biliyorsanız kapasiteyi constructor'a verin; builder hiç büyümek zorunda kalmaz:

Çıktı:

12000
10000
True
True

Önceden boyutlandırma bir optimizasyondur, zorunluluk değil. Büyüme yeni bir blok ayırır ama mevcut metni asla taşımaz, bu yüzden sıcak bir yolda birçok büyük string oluşturmuyorsanız varsayılan yeterlidir.

StringBuilder ne zaman kullanılmamalı

StringBuilder her + için daha hızlı bir yedek değildir. Derleyici tek bir ifadedeki a + b + c + d'yi zaten son uzunluğu hesaplayan ve her parçayı bir kez kopyalayan tek bir string.Concat çağrısına çevirir; bu, bir builder'ın daha fazla kodla yapacağı işin aynısıdır. Şu durumlarda düz string'leri kullanın:

  • Parça sayısı az ve sabitse: $"{first} {last} ({age})".
  • Parçalar bir koleksiyondaysa ve bir ayırıcı gerekiyorsa: string.Join(", ", items).
  • Parçalar ayırıcı gerektirmiyorsa: string.Concat(parts).
  • Herhangi bir döngünün dışında birkaç kez birleştiriyorsanız.

Bir döngü içinde ekleme yaptığınızda, önceden bilmediğiniz sayıda ekleme yaptığınızda ya da metni oluştururken düzenlediğinizde (ekleme, değiştirme, silme) StringBuilder kullanın. İleri geri dönüştürmek (döngünün içinde sb.ToString()) faydayı çöpe atar; onu en sonda bir kez çağırın.

Diğer alternatif stream'lerdir: büyük bir dosya üretmek için tüm içeriği bellekte oluşturmak yerine satırları ilerledikçe bir StreamWriter'a yazın. Bkz. dosyalar.

Sıkça Sorulan Sorular

C#'ta StringBuilder nedir?

System.Text.StringBuilder, bir string'i birçok adımda oluşturmak için değiştirilebilir bir karakter tamponudur. Append her seferinde yeni bir string yaratmak yerine tamponun sonuna ekler ve ToString() son string'i bir kez üretir. Bir döngüde metin oluşturmanın standart aracıdır.

String birleştirme yerine ne zaman StringBuilder kullanmalıyım?

Bir döngüde ya da bilinmeyen sayıda ekleme yaptığınızda; örneğin bir koleksiyondan rapor, CSV dosyası ya da HTML oluştururken. Sabit birkaç parça için (a + b + c ya da tek bir interpolated string) düz birleştirme aynı hızdadır ve daha kolay okunur, çünkü derleyici onu zaten tek bir string.Concat çağrısında birleştirir.

Bir StringBuilder'dan son karakter nasıl silinir?

Length'ini azaltın: if (sb.Length > 0) sb.Length--; son karakteri kopyalamadan atar. sb.Remove(sb.Length - 1, 1) aynı işi yapar. Sondaki virgülü silmenin olağan yolu budur, ama string.Join sondaki ayırıcıyı en baştan önler.

StringBuilder thread-safe mi?

Hayır. Aynı StringBuilder üzerinde birkaç thread'den aynı anda Append çağırmak içeriğini bozabilir ya da istisna fırlatabilir. Her thread'e kendi builder'ını verin ve sonuçları birleştirin ya da paylaşılanı bir lock ile koruyun.

C#'ta AppendLine ne ekler?

Metni ve ardından Windows'ta \r\n, Linux ve macOS'ta \n olan Environment.NewLine'ı. Çıktının belirli bir satır sonu kullanması gerekiyorsa, örneğin bir dosya biçimi için \n ya da bir HTTP başlığı için \r\n, bunun yerine onu Append("\n") ile açıkça ekleyin.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA