Il modo più efficiente per concatenare le stringhe?


286

Qual è il modo più efficiente per concatenare le stringhe?


9
Voglio mettere qui un avvertimento preminente che la risposta accettata è significativamente incompleta perché non discute tutti i casi rilevanti.
usr

@usr In effetti ... informazioni più dettagliate sui StringBuildercasi d'uso sono disponibili qui .
Tamir Vered,

Il mio nuovo preferito di C # 6 è $ "Testo costante qui {foo} e {bar}" ... è come String.Formatsugli steroidi. Il che, per quanto riguarda le prestazioni, è leggermente più lento di una linea rispetto +e String.Concat, ma molto meglio di quelli, sebbene più lento di StringBuilder, a più chiamate. In pratica, le differenze di prestazioni sono tali che, se dovessi scegliere un solo modo per concatenare, sceglierei interpolazioni di stringhe usando $... Se due modi, quindi aggiungo StringBuilderalla mia casella degli strumenti. Con questi due modi sei pronto.
u8it

La String.Joinrisposta che segue non rende +giustizia ed è, in pratica, un brutto modo di concatenare le stringhe, ma è sorprendentemente veloce in termini di prestazioni. La risposta è interessante. String.Concate String.Joinpuò agire entrambi su array, ma in String.Joinrealtà è più veloce. Apparentemente, String.Joinè piuttosto sofisticato e più ottimizzato di String.Concat, in parte perché funziona in modo simile al fatto StringBuilderche calcola prima la lunghezza della stringa e quindi costruisce la stringa che beneficia di questa conoscenza utilizzando UnSafeCharBuffer.
u8it

Ok, quindi è veloce, ma String.Joinrichiede anche la costruzione di un array che sembra inefficace in termini di risorse, giusto? ... Scoprilo +e String.Concatcostruisci comunque array per i loro componenti. Di conseguenza, creare manualmente un array e alimentarlo String.Joinè relativamente più veloce ... tuttavia, StringBuildersovraperforma String.Joinin quasi tutti i modi pratici mentre $è solo leggermente più lento e molto più veloce a stringhe lunghe ... per non parlare del fatto che è scomodo e brutto da usare String.Joinse si ha per creare un array per esso sul posto.
u8it

Risposte:


155

Il StringBuilder.Append()metodo è molto meglio dell'utilizzo +dell'operatore. Ma ho scoperto che, quando si eseguono 1000 concatenazioni o meno, String.Join()è ancora più efficiente di StringBuilder.

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

L'unico problema String.Joinè che devi concatenare le stringhe con un delimitatore comune.

Modifica: come ha sottolineato @ryanversaw , puoi creare il delimitatore string.Empty.

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

11
StringBuilderha un costo di avvio paragonabile enorme, è efficiente solo se utilizzato con stringhe molto grandi o molte concatenazioni. Non è banale scoprirlo per una determinata situazione. Se le prestazioni sono problematiche, la profilazione è tua amica (controlla ANTS).
Abel

32
Questo non è vero per la concatenazione su linea singola. Supponi di fare myString = "pippo" + var1 + "bar" + var2 + "ciao" + var3 + "mondo", il compilatore lo trasforma automaticamente in una chiamata string.concat, che è il più efficiente possibile. Questa risposta è errata, ci sono molte risposte migliori tra cui scegliere
csauve,

2
Per una banale concatenazione di stringhe usa ciò che mai è più leggibile. stringa a = b + c + d; sarà quasi sempre più veloce di farlo con StringBuilder, ma la differenza è in genere irrilevante. Utilizzare StringBuilder (o un'altra opzione a scelta) quando si aggiunge ripetutamente alla stessa stringa (ad es. Compilando un report) o quando si utilizzano stringhe di grandi dimensioni.
Swanny,

5
Perché non l'hai menzionato string.Concat?
Venemo,

272

Rico Mariani , il guru delle prestazioni .NET, aveva un articolo su questo argomento. Non è così semplice come si potrebbe sospettare. Il consiglio di base è questo:

Se il tuo modello è simile a:

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

questo è un concat ed è scattante, StringBuilder probabilmente non aiuterà.

Se il tuo modello è simile a:

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

allora probabilmente vuoi StringBuilder.

Ancora un altro articolo a supporto di questa affermazione proviene da Eric Lippert in cui descrive +in modo dettagliato le ottimizzazioni eseguite su concatenazioni di una riga .


1
Che dire String.Format ()?
IronSlug,

86

Esistono 6 tipi di concatenazioni di stringhe:

  1. Usando il +simbolo più ( ).
  2. Usando string.Concat().
  3. Usando string.Join().
  4. Usando string.Format().
  5. Usando string.Append().
  6. Usando StringBuilder.

In un esperimento, è stato dimostrato che string.Concat()è il modo migliore per avvicinarsi se le parole sono meno di 1000 (circa) e se le parole sono più di 1000, allora StringBuilderdovrebbe essere usato.

Per ulteriori informazioni, consultare questo sito .

string.Join () vs string.Concat ()

Il metodo string.Concat qui equivale alla chiamata del metodo string.Join con un separatore vuoto. L'aggiunta di una stringa vuota è veloce, ma non farlo è ancora più veloce, quindi il metodo string.Concat sarebbe superiore qui.


4
Dovrebbe leggerlo è stato provato string.Concat () o + è il modo migliore. Sì, posso ottenerlo dall'articolo ma mi fa risparmiare un clic. Quindi, + e concat si compilano nello stesso codice.
brumScouse

Ho usato questa base per cercare di rendere il mio metodo più efficiente, in cui avevo solo bisogno di concatenare esattamente 3 stringhe. Ho scoperto che in +realtà era 3 millisecondi più veloce di string.Concat(), anche se non ho esaminato la quantità di stringhe richieste prima degli string.Concat()outraces +.
Gnemlock

59

Da Chinh Do - StringBuilder non è sempre più veloce :

Regole pratiche

  • Quando si concatenano tre valori di stringa dinamici o meno, utilizzare la concatenazione di stringhe tradizionale.

  • Quando si concatenano più di tre valori di stringa dinamici, utilizzare StringBuilder.

  • Quando si crea una stringa grande da più letterali stringa, utilizzare l' @operatore letterale stringa o inline +.

Il più delle volte StringBuilderè la soluzione migliore, ma ci sono casi come mostrato in quel post che dovresti almeno pensare a ogni situazione.


8
afaik @ disattiva solo l'elaborazione delle sequenze di escape. msdn.microsoft.com/en-us/library/362314fe.aspx d'accordo
abatishchev

12

Se stai operando in un ciclo, StringBuilderè probabilmente la strada da percorrere; ti fa risparmiare il sovraccarico di creare nuove stringhe regolarmente. Nel codice che verrà eseguito solo una volta, tuttavia, String.Concatprobabilmente va bene.

Tuttavia, Rico Mariani (guru dell'ottimizzazione di .NET) ha formulato un quiz in cui ha dichiarato alla fine che, nella maggior parte dei casi, raccomanda String.Format.


Raccomando l'uso di string.format su string + string da anni alle persone con cui ho lavorato. Penso che i vantaggi della leggibilità siano un vantaggio aggiuntivo oltre al vantaggio delle prestazioni.
Scott Lawrence,

1
Questa è la vera risposta corretta. La risposta attualmente accettata per StringBuilder è errata, in quanto non menziona gli accodamenti a riga singola per i quali string.concat o + è più veloce. Il fatto poco noto è che il compilatore in realtà traduce + in string.concat. Inoltre, per i loop o per i concatari su più righe uso un generatore di stringhe personalizzato che si aggiunge solo quando viene chiamato .ToString - superando il problema indeterminato del buffer che StringBuilder ha
csauve

2
string.Format non è il modo più veloce in nessun caso. Non so come escogitare un caso in cui viene avanti.
usr

@usr - nota che Rico non dice esplicitamente che è il più veloce , solo che è il suo consiglio: "Anche se è il peggior rendimento, e lo sapevamo con largo anticipo, entrambi i tuoi CLR Performance Architects concordano sul fatto che [string.Format] dovrebbe essere la scelta predefinita. Nel caso altamente improbabile che diventi un problema perfetto, il problema è prontamente risolvibile con solo modesti cambiamenti locali. Normalmente stai solo incassando una buona manutenibilità. "
Adam V,

@AdamV la domanda riguarda il modo più veloce. Non sono d'accordo sul fatto che sia la scelta predefinita, anche se non per ragioni perfette. Può essere una sintassi goffa. Resharper può convertire avanti e indietro a piacimento.
usr

10

Ecco il metodo più veloce che ho sviluppato nel corso di un decennio per la mia app NLP su larga scala. Ho varianti per IEnumerable<T>e altri tipi di input, con e senza separatori di diversi tipi ( Char, String), ma qui mostro il semplice caso di concatenare tutte le stringhe di un array in una singola stringa, senza separatore. L'ultima versione qui è sviluppata e testata in unità su C # 7 e .NET 4.7 .

Esistono due chiavi per prestazioni più elevate; il primo è pre-calcolare l'esatta dimensione totale richiesta. Questo passaggio è banale quando l'input è un array come mostrato qui. Per la gestione IEnumerable<T>, invece, vale la pena prima raccogliere le stringhe in un array temporaneo per calcolare quel totale (l'array è necessario per evitare di chiamare ToString()più di una volta per elemento poiché tecnicamente, data la possibilità di effetti collaterali, farlo potrebbe cambiare la semantica prevista di un'operazione 'string join').

Successivamente, data la dimensione di allocazione totale della stringa finale, la maggiore spinta in termini di prestazioni si ottiene costruendo la stringa di risultati sul posto . Ciò richiede la tecnica (forse controversa) di sospendere temporaneamente l'immutabilità di un nuovo Stringche inizialmente è allocato pieno di zeri. Qualsiasi controversia a parte, tuttavia ...

... nota che questa è l'unica soluzione di concatenazione di massa in questa pagina che evita del tutto un ulteriore round di allocazione e copia da parte del Stringcostruttore.

Codice completo:

/// <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);

Dovrei menzionare che questo codice ha una leggera modifica rispetto a quello che uso io stesso. Nell'originale, chiamo l' istruzione cpblk IL da C # per eseguire la copia effettiva. Per semplicità e portabilità nel codice qui, ho sostituito quello con P / Invoke memcpyinvece, come puoi vedere. Per le massime prestazioni su x64 ( ma forse non su x86 ) potresti invece utilizzare il metodo cpblk .


string.Joinfa tutte queste cose già per te. Non è necessario scriverlo da soli. Calcola la dimensione della stringa finale, costruisce una stringa di quella dimensione e quindi scrive nella matrice di caratteri sottostante. Ha anche il vantaggio di utilizzare nomi di variabili leggibili nel processo.
Servito il

1
@Servy Grazie per il commento; anzi String.Joinpuò essere efficiente. Come ho accennato nell'introduzione, il codice qui è solo l'illustrazione più semplice di una famiglia di funzioni che uso per scenari che String.Joino non gestisce (come l'ottimizzazione per il Charseparatore) o che non gestiva nelle versioni precedenti di .NET. Suppongo che non avrei dovuto scegliere questo per l'esempio più semplice, dal momento che è un caso che String.Joingestisce già bene, anche se con "l'inefficienza", probabilmente non misurabile, dell'elaborazione di un separatore vacuo, vale a dire. String.Empty.
Glenn Slayden,

Certo, nel caso in cui tu non abbia un separatore, dovresti chiamare Concat, il che lo fa anche correttamente. In entrambi i casi non è necessario scrivere il codice da soli.
Servito il

7
@Servy Ho confrontato le prestazioni String.Joinrispetto al mio codice usando questo cablaggio di prova . Per 10 milioni di operazioni di concatenazione casuali fino a 100 stringhe di dimensioni ciascuna, il codice mostrato sopra è costantemente più veloce del 34% rispetto String.Joinalla versione di rilascio x64 con .NET 4.7 . Poiché l'OP richiede esplicitamente il metodo "più efficiente", il risultato suggerisce che la mia risposta si applica. Se questo risolve i tuoi dubbi, ti invito a riconsiderare il tuo voto negativo.
Glenn Slayden,

1
Di recente ho eseguito il benchmarking su x64 CLR 4.7.1 completo e l'ho trovato circa due volte più veloce della stringa. Unisciti con circa il 25% in meno di memoria allocata ( i.imgur.com/SxIpEmL.png ) quando usi cpblk o github.com/ JonHanna / Mnemosyne
quentin-starin il

6

Da questo articolo MSDN :

C'è un sovraccarico associato alla creazione di un oggetto StringBuilder, sia nel tempo che nella memoria. Su una macchina con memoria veloce, StringBuilder diventa utile se si eseguono circa cinque operazioni. Come regola generale, direi che 10 o più operazioni sulle stringhe sono una giustificazione per il sovraccarico su qualsiasi macchina, anche se più lenta.

Quindi, se ti fidi di MSDN, vai con StringBuilder se devi fare più di 10 operazioni / concatenazioni di stringhe, altrimenti un semplice concat di stringa con '+' va bene.



5

Aggiungendo alle altre risposte, tieni presente che StringBuilder può ricevere una quantità iniziale di memoria da allocare .

Il parametro capacity definisce il numero massimo di caratteri che possono essere memorizzati nella memoria allocata dall'istanza corrente. Il suo valore è assegnato alla proprietà Capacity . Se il numero di caratteri da archiviare nell'istanza corrente supera questo valore di capacità , l'oggetto StringBuilder alloca memoria aggiuntiva per memorizzarli.

Se la capacità è zero, viene utilizzata la capacità predefinita specifica dell'implementazione.

L'aggiunta ripetuta a StringBuilder che non è stata pre-allocata può comportare molte allocazioni non necessarie, proprio come concatenare ripetutamente stringhe regolari.

Se sai quanto sarà lunga la stringa finale, puoi calcolarla banalmente o fare un'ipotesi colta sul caso comune (allocare troppo non è necessariamente una cosa negativa), dovresti fornire queste informazioni al costruttore o al Proprietà di capacità . Soprattutto quando si eseguono test delle prestazioni per confrontare StringBuilder con altri metodi come String.Concat, che fanno la stessa cosa internamente. Qualsiasi test che vedi online che non include la pre-allocazione di StringBuilder nei suoi confronti è sbagliato.

Se non riesci a fare alcun tipo di ipotesi sulla dimensione, probabilmente stai scrivendo una funzione di utilità che dovrebbe avere il suo argomento opzionale per controllare la pre-allocazione.


4

Di seguito potrebbe essere un'altra soluzione alternativa per concatenare più stringhe.

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

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

interpolazione di stringhe


1
Questo è in realtà sorprendente come metodo generale di concatenazione. Fondamentalmente è String.Formatpiù leggibile e più facile da lavorare. Benchmark, è leggermente più lento di +e String.Concatad una linea di concatenazioni, ma molto meglio di entrambi quelli delle chiamate ripetitive che rendono StringBuildermeno necessario.
u8it

2

Il più efficiente è usare StringBuilder, in questo modo:

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

@jonezy: String.Concat va bene se hai un paio di piccole cose. Ma se stai concatenando megabyte di dati, il tuo programma probabilmente si riempirà.


ehi qual è la soluzione per megabyte di dati?
Neel

2

Prova questi 2 pezzi di codice e troverai la soluzione.

 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();
    }

Scoprirai che il 1 ° codice finirà molto velocemente e la memoria sarà in buona quantità.

Il secondo codice forse la memoria sarà ok, ma ci vorrà più tempo ... molto più tempo. Quindi, se hai un'applicazione per molti utenti e hai bisogno di velocità, usa il 1 °. Se hai un'app per un'app per un utente a breve termine, forse puoi usare entrambe o la seconda sarà più "naturale" per gli sviluppatori.

Saluti.


1

Per solo due stringhe, sicuramente non vuoi usare StringBuilder. Esiste una soglia oltre la quale l'overhead StringBuilder è inferiore all'overhead dell'allocazione di più stringhe.

Quindi, per più di 2-3 stringhe, usa il codice di DannySmurf . Altrimenti, basta usare l'operatore +.


1

System.String è immutabile. Quando modifichiamo il valore di una variabile stringa, una nuova memoria viene allocata al nuovo valore e la precedente allocazione di memoria rilasciata. System.StringBuilder è stato progettato per avere il concetto di una stringa mutabile in cui è possibile eseguire una varietà di operazioni senza allocare una posizione di memoria separata per la stringa modificata.


1

Un'altra soluzione:

all'interno del ciclo, usa Elenco invece di stringa.

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

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

è molto molto veloce.



0

Dipenderebbe dal codice. StringBuilder è generalmente più efficiente, ma se stai concatenando solo poche stringhe e stai facendo tutto in una riga, probabilmente le ottimizzazioni del codice si prenderanno cura di te. È importante pensare anche a come appare il codice: per i set più grandi StringBuilder renderà più facile la lettura, per i più piccoli StringBuilder aggiungerà semplicemente disordine inutile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.