Dizeleri birleştirmenin en etkili yolu?


286

Dizeleri birleştirmenin en etkili yolu nedir?


9
Burada, kabul edilen cevabın önemli ölçüde eksik olduğuna dair önemli bir uyarı vermek istiyorum, çünkü ilgili tüm vakaları tartışmıyor.
usr

@usr Gerçekten ... StringBuilderkullanım durumları hakkında daha ayrıntılı bilgiyi burada bulabilirsiniz .
Tamir Vered

C # 6'dan yeni favorim $ "Sabit metin burada {foo} ve {bar}" ... String.Formatsteroidlerde olduğu gibi . Hangi, performans açısından, bir daha gömlekleri bir nebze daha yavaştır +ve String.Concatolsa daha yavaş, fakat çok daha iyi daha StringBuilder, birden çok sesli arama at. Pratik olarak, performans farklılıkları öyle ki, birleştirmek için sadece bir yol seçmek zorunda kalsaydım, kullanarak dize enterpolasyonlarını seçerdim $... İki yol varsa, o zaman StringBuilderaraç kutuma ekle . Ayarladığınız bu iki yolla.
u8it

Aşağıdaki String.Joincevap, +adalet yapmaz ve pratik olarak, dizeleri birleştirmek için kötü bir yoldur, ancak şaşırtıcı derecede hızlı performans açısından akıllıcadır. Cevabı neden ilginç. String.Concatve String.Joinher ikisi de diziler üzerinde etkili olabilir, ancak String.Joinaslında daha hızlıdır. Görünüşe String.Joingöre String.Concat, kısmen StringBuilderilk önce dize uzunluğunu hesapladığı ve daha sonra UnSafeCharBuffer kullanarak bu bilgiden yararlanan dize oluşturduğu için benzer şekilde çalıştığı için oldukça sofistike ve daha optimize edilmiştir .
u8it

Tamam, bu yüzden hızlı, ama String.Joinaynı zamanda kaynak verimsiz görünen bir dizi oluşturmayı gerektiriyor mu? ... Bunu ortaya koyuyor +ve String.Concatyine de kendi bileşenleri için diziler oluşturuyor. Sonuç olarak, manuel olarak bir dizi oluşturmak ve onu beslemek String.Joinnispeten daha hızlıdır ... ancak, StringBuilderyine de String.Joinher pratik yoldan daha iyi performans gösterirken $, uzun dizelerde sadece biraz daha yavaş ve çok daha hızlıdır ... String.Joinvarsa , kullanmanın garip ve çirkin olduğunu belirtmemek gerekir. yerinde bir dizi oluşturmak için.
u8it

Yanıtlar:


155

StringBuilder.Append()Yöntem çok daha iyi kullanarak daha +operatörü. Ancak, 1000 veya daha az bitiştirme gerçekleştirirken, String.Join()daha da etkili olduğunu fark ettim StringBuilder.

StringBuilder sb = new StringBuilder();
sb.Append(someString);

Tek sorun String.Joindizeleri ortak bir sınırlayıcı ile birleştirmek zorunda olmasıdır.

Düzenleme: @ryanversaw'ın işaret ettiği gibi , sınırlayıcıyı yapabilirsiniz string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });

11
StringBuilderbüyük bir karşılaştırılabilir başlangıç ​​maliyetine sahiptir, yalnızca çok büyük dizelerle veya çok fazla birleştirmeyle kullanıldığında etkilidir. Herhangi bir durumu bulmak önemsiz değildir. Performans söz konusuysa profil oluşturma sizin dostunuzdur (ANTS'yi kontrol edin).
Abel

32
Bu, tek satır birleştirme için geçerli değildir. Diyelim ki myString = "foo" + var1 + "bar" + var2 + "merhaba" + var3 + "dünya", derleyici bunu otomatik olarak bir string.concat çağrısına dönüştürür. Bu cevap yanlış, aralarından seçim yapabileceğiniz çok daha iyi cevaplar var
csauve

2
Önemsiz dize birleşmesi için en okunabilir olanı kullanın. dize a = b + c + d; neredeyse her zaman StringBuilder ile yapmaktan daha hızlı olacaktır, ancak fark genellikle önemsizdir. Aynı dizeye art arda eklerken (örneğin bir rapor oluştururken) veya büyük dizelerle uğraşırken StringBuilder'i (veya seçtiğiniz başka bir seçeneği) kullanın.
Swanny

5
Neden bahsetmedin string.Concat?
Venemo

272

.NET Performans gurusu Rico Mariani'nin bu konuda bir makalesi vardı . Şüphelendiğiniz kadar basit değil. Temel tavsiye şudur:

Deseniniz aşağıdaki gibi görünüyorsa:

x = f1(...) + f2(...) + f3(...) + f4(...)

Bu bir concat ve zippy, StringBuilder muhtemelen yardımcı olmaz.

Deseniniz aşağıdaki gibi görünüyorsa:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

muhtemelen StringBuilder'ı istersiniz.

Bu iddiayı destekleyen başka bir makale Eric Lippert'den geliyor ve burada bir satırda yapılan +birleşmelerde yapılan optimizasyonları detaylı bir şekilde anlatıyor .


1
String.Format () hakkında ne dersiniz?
IronSlug

86

6 tür dize birleşimi vardır:

  1. Plus ( +) sembolünü kullanarak .
  2. Kullanma string.Concat().
  3. Kullanma string.Join().
  4. Kullanma string.Format().
  5. Kullanma string.Append().
  6. Kullanma StringBuilder.

Bir deneyde, string.Concat()kelimeler 1000'den azsa (yaklaşık olarak) ve kelimeler 1000'den fazla ise yaklaşmanın en iyi yolu olduğu kanıtlanmıştır StringBuilder.

Daha fazla bilgi için bu siteyi kontrol edin .

string.Join () ve string.Concat () karşılaştırması

Buradaki string.Concat yöntemi, boş bir ayırıcıyla string.Join yönteminin çağrılmasına eşdeğerdir. Boş bir dize eklemek hızlıdır, ancak bunu yapmamak daha da hızlıdır, bu nedenle string.Concat yöntemi burada daha üstün olacaktır.


4
Okumalıydı string.Concat () veya + kanıtlanmış en iyi yoldur. Evet bunu makaleden alabilirim ama bana tek bir tıklama kazandırdı. Böylece, + ve concat aynı koda derlenir.
13'te brum

Bu temeli, sadece 3 dizeyi birleştirmeye ihtiyaç duyduğum bir yöntemi daha verimli hale getirmek için kullandım. Ben outraces önce gerekli dizeleri miktarına bakmadım rağmen +, aslında 3 milisaniye daha hızlı bulundu . string.Concat()string.Concat()+
Gnemlock

59

Chinh Do Gönderen - StringBuilder her zaman daha hızlı değildir :

Temel Kurallar

  • Bitiştirmek zaman üç daha az dinamik dize değerleri veya geleneksel dize birleştirme kullanın.

  • Üçten fazla dinamik dize değerini birleştirirken kullanın StringBuilder.

  • Birkaç dize değişmezinden büyük bir dize oluştururken, @dize değişmezini veya satır içi + işlecini kullanın.

Çoğu zaman StringBuilderen iyi bahsinizdir, ancak bu yayında gösterildiği gibi, en azından her durumu düşünmeniz gereken durumlar vardır.


8
afaik @ sadece kaçış dizileri işlemeyi kapatır. msdn.microsoft.com/tr-tr/library/362314fe.aspx katılıyorum
abatishchev

12

Eğer bir döngü içinde çalışıyorsanız, StringBuildermuhtemelen gitmek için bir yoldur; düzenli olarak yeni dizeler oluşturma yükünü azaltır. Ancak sadece bir kez çalışacak kodda String.Concatmuhtemelen iyi.

Bununla birlikte, Rico Mariani (.NET optimizasyon gurusu) sonunda, çoğu durumda tavsiye ettiğini belirttiği bir sınav oluşturdu String.Format.


Yıllardır birlikte çalıştığım insanlara string.format üzerinden string.format kullanımını öneriyorum. Okunabilirlik avantajlarının performans avantajının ötesinde ek bir avantaj olduğunu düşünüyorum.
Scott Lawrence

1
Bu gerçek doğru cevaptır. Şu anda kabul edilen StringBuilder yanıtı, string.concat veya + 'nın daha hızlı olduğu tek satır eklerinden bahsetmediğinden yanlıştır. Bilinen gerçek şu ki, derleyici aslında + 'ları string.concat's' e çevirir. Ayrıca, döngüler veya birden çok satır birleşimleri için yalnızca .ToString çağrıldığında eklenen - StringBuilder'in sahip olduğu belirsiz arabellek sorununun üstesinden gelen - özel olarak oluşturulmuş bir dize oluşturucu kullanıyorum
csauve

2
string.Format hiçbir koşulda en hızlı yol değildir. Geleceği bir davayı nasıl ele alacağımı bilmiyorum.
usr

@usr - Rico'nun açıkça en hızlı olduğunu söylemediğini , sadece onun önerisi olduğunu unutmayın: "En kötü performansa rağmen ve önceden çok şey biliyorduk, CLR Performance Architects'inizin her ikisi de [string.Format] Mükemmel bir sorun haline gelmesi olası olmayan bir durumda, sorun sadece mütevazı yerel değişikliklerle kolayca ele alınabilir. Normalde sadece güzel bir bakımdan ödünç veriyorsunuz. "
Adam V

@AdamV soru en hızlı yol hakkında. Mükemmel nedenlerle olmasa da varsayılan seçim olmak hakkında nefret ediyorum. Bu beceriksiz sözdizimi olabilir. Yeniden birleştirici istediği zaman ileri geri dönüştürebilir.
usr

10

İşte büyük ölçekli NLP uygulamam için on yıldan fazla bir süredir geliştirdiğim en hızlı yöntem. Ben IEnumerable<T>farklı türler ( Charve String) ayırıcılar ile ve olmadan, ve diğer giriş türleri için varyasyonları var , ama ben burada bir dizideki tüm dizeleri ayırıcı olmadan, tek bir dizeye bitirme basit bir durum gösterir . Buradaki en son sürüm, C # 7 ve .NET 4.7 üzerinde geliştirilmiş ve birim olarak test edilmiştir .

Daha yüksek performans için iki anahtar vardır; birincisi, gereken tam boyutun önceden hesaplanmasıdır. Giriş, burada gösterildiği gibi bir dizi olduğunda bu adım önemsizdir. IEnumerable<T>Bunun yerine işlemek için, toplamı hesaplamak için önce dizeleri geçici bir dizide toplamaya değer (Dizi, aramadan kaçınmak için gereklidirToString() yan etkiler olasılığı göz önüne alındığında, teknik olarak, her bir öğe için birden fazla 'dize birleştirme' işlemi).

Daha sonra, son dizenin toplam tahsis boyutu göz önüne alındığında, performans dizisindeki en büyük artış , sonuç dizesini yerinde oluşturarak elde edilir . Bunu yapmak için String, başlangıçta sıfırlarla dolu olan yeni bir şeyin değişmezliğini geçici olarak askıya alma (belki de tartışmalı) tekniği gerekir . Ancak böyle bir tartışma bir yana ...

... bunun bu sayfada yapıcı tarafından fazladan bir tahsis ve kopyalama turundan tamamen kaçınan tek toplu birleştirme çözümü olduğunu unutmayın String.

Tam kod:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

Bu kodun kendimi kullandığımdan küçük bir değişiklik olduğunu belirtmeliyim. Orijinal, ben çağrı cpblk IL talimat dan C # fiili kopyalama yapmak. Buradaki koddaki basitlik ve taşınabilirlik için memcpy, gördüğünüz gibi, bunun yerine P / Invoke ile değiştirdim . X64'te ( ancak belki x86'da değil ) en yüksek performans için, bunun yerine cpblk yöntemini kullanmak isteyebilirsiniz .


string.Joinbütün bunları zaten sizin için yapıyor. Kendiniz yazmaya gerek yok. Son dizenin boyutunu hesaplar, bu boyutta bir dize oluşturur ve ardından alttaki karakter dizisine yazar. Süreçte okunabilir değişken adları kullanma bonusu bile vardır.
Servy

1
@Servy Yorum için teşekkürler; gerçekten String.Joinverimli olabilir. Girişte ima ettiğim gibi, buradaki kod, senaryoları için kullandığım String.Join( Charayırıcı için optimize etme gibi ) veya .NET'in önceki sürümlerinde işlemeyen senaryolar için kullandığım en basit işlev örneğidir . Sanırım bunu en basit örnek için seçmemeliydim, çünkü String.Joinboş bir ayırıcı, viz "ölçülemez", muhtemelen ölçülemez olsa da, zaten iyi işleyen bir durum . String.Empty.
Glenn Slayden

Elbette, bir ayırıcıya sahip değilseniz, o zaman aramalısınız Concat, bu da bunu doğru bir şekilde yapar. Her iki durumda da kodu kendiniz yazmanıza gerek yoktur.
Servy

7
@Servy Bu test kayışını String.Joinkullanarak koduma karşı performansı karşılaştırdım . 100 kelime boyutlu dizeleri her kadar 10 milyon rastgele birleştirme işlemleri için, yukarıda gösterilen kod% 34 daha hızlı devamlı olarak üzerine 64 ile yayın oluşturma .NET 4.7 . OP açıkça "en verimli" yöntemi istediğinden, sonuç cevabımın geçerli olduğunu gösteriyor. Bu endişelerinizi giderirse, sizi oyunuzu tekrar gözden geçirmeye davet ediyorum. String.Join
Glenn Slayden

1
Son zamanlarda bunu x64 tam CLR 4.7.1'de karşılaştırdım ve dizenin yaklaşık iki katı kadar hızlı buldum. Cpblk veya github.com/ kullanırken yaklaşık% 25 daha az bellek ayırın ( i.imgur.com/SxIpEmL.png ) ile katılın. JonHanna / Mnemosyne
quentin-

6

Bu MSDN makalesinden :

Hem zaman hem de bellek olarak bir StringBuilder nesnesi oluşturmayla ilişkili bazı ek yükler vardır. Hızlı belleğe sahip bir makinede, yaklaşık beş işlem yapıyorsanız StringBuilder işe yarar. Genel bir kural olarak, 10 veya daha fazla dize işleminin herhangi bir makinedeki ek yük için bir gerekçe olduğunu, hatta daha yavaş olduğunu söyleyebilirim.

Eğer 10'dan fazla dizgi işlemi / birleştirme yapmanız gerekiyorsa MSDN'e güveniyorsanız StringBuilder ile gidin - aksi takdirde '+' ile basit dize concat gayet iyi.



5

Diğer cevaplara ek olarak, StringBuilder'a tahsis edilecek ilk bellek miktarının söylenebileceğini lütfen unutmayın .

Kapasiteli parametre geçerli örneği tarafından ayrılmış bellekte saklanabilir karakter sayısını tanımlar. Değeri Capacity özelliğine atanır . Geçerli örnekte depolanacak karakter sayısı bu kapasite değerini aşarsa , StringBuilder nesnesi bunları saklamak için ek bellek ayırır.

Eğer kapasite sıfırdır, uygulama özgü varsayılan kapasitesi kullanılır.

Önceden tahsis edilmemiş bir StringBuilder öğesine art arda eklemek, normal dizeleri tekrar tekrar birleştirmek gibi birçok gereksiz ayırmaya neden olabilir.

Son dizenin ne kadar süreceğini biliyorsanız, önemsiz bir şekilde hesaplayabilir veya ortak durum hakkında eğitimli bir tahmin yapabilir (çok fazla ayırmak mutlaka kötü bir şey değildir), bu bilgileri yapıcıya veya Kapasite özelliği. Özellikle StringBuilder'ı dahili olarak aynı şeyi yapan String.Concat gibi diğer yöntemlerle karşılaştırmak için performans testleri çalıştırırken. Çevrimiçi olarak gördüğünüz ve karşılaştırmalarında StringBuilder ön ayırmasını içermeyen tüm testler yanlıştır.

Boyut hakkında herhangi bir tahminde bulunamıyorsanız, büyük olasılıkla ön ayırmayı denetlemek için kendi isteğe bağlı argümanına sahip olması gereken bir yardımcı program işlevi yazıyorsunuzdur.


4

Birden fazla dizeyi birleştirmek için bir alternatif çözüm daha aşağıda olabilir.

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

dize enterpolasyonu


1
Bu aslında genel bir birleştirme yöntemi olarak şaşırtıcıdır. Temelde String.Formatancak daha okunabilir ve çalışması daha kolaydır. Bench-işaretleme onu, o daha yavaş olduğunu +ve String.Concatbir hat concatenations adresindeki ama çok daha iyi olanların hem daha yapma tekrarlayan çağrılar en StringBuilderaz gerekli.
u8it

2

En verimli StringBuilder'ı kullanmaktır, şöyle:

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy: String.Concat birkaç küçük şeyiniz varsa sorun yok. Ancak megabaytlarca veriyi birleştiriyorsanız, programınız büyük olasılıkla tanınır.


hey megabayt veri için çözüm nedir?
Neel

2

Bu 2 kod parçasını deneyin ve çözümü bulacaksınız.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Vs

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

1. kodun gerçekten hızlı biteceğini ve hafızanın iyi olduğunu göreceksiniz.

İkinci kod belki hafıza iyi olacak, ama daha uzun sürecek ... çok daha uzun. Bu nedenle, çok sayıda kullanıcı için bir uygulamanız varsa ve hıza ihtiyacınız varsa, 1.'i kullanın. Kısa vadeli bir kullanıcı uygulaması için bir uygulamanız varsa, belki ikisini de kullanabilirsiniz veya 2. geliştiriciler için daha "doğal" olacaktır.

Şerefe.


1

Sadece iki dize için kesinlikle StringBuilder kullanmak istemezsiniz. Üzerinde StringBuilder ek yükünün birden fazla dize tahsis etme yükünden daha az olduğu bir eşik vardır.

2-3 dizeden fazlası için DannySmurf kodunu kullanın . Aksi takdirde, + operatörünü kullanmanız yeterlidir.


1

System.String değişmez. Bir string değişkeninin değerini değiştirdiğimizde, yeni değere yeni bir bellek tahsis edilir ve önceki bellek tahsisi serbest bırakılır. System.StringBuilder, değiştirilmiş dize için ayrı bir bellek konumu tahsis edilmeden çeşitli işlemlerin gerçekleştirilebileceği değiştirilebilir bir dize konseptine sahip olacak şekilde tasarlanmıştır.


1

Başka bir çözüm:

döngü içinde dize yerine Liste'yi kullanın.

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

çok çok hızlı.



0

Bu koda bağlıdır. StringBuilder genel olarak daha verimlidir, ancak yalnızca birkaç dizeyi birleştirir ve hepsini tek bir satırda yapıyorsanız, kod optimizasyonları muhtemelen sizin için ilgilenir. Kodun nasıl göründüğünü düşünmek önemlidir: daha büyük setler için StringBuilder okumayı kolaylaştıracak, küçük olanlar için StringBuilder sadece gereksiz dağınıklık ekleyecektir.

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.