Der effizienteste Weg, um Strings zu verketten?

285

Was ist der effizienteste Weg, um Zeichenfolgen zu verketten?

jimmij
quelle
9
Ich möchte hier deutlich darauf hinweisen, dass die akzeptierte Antwort erheblich unvollständig ist, da nicht alle relevanten Fälle erörtert werden.
usr
@usr In der Tat ... detailliertere Informationen zu StringBuilderAnwendungsfällen finden Sie hier .
Tamir Vered
Mein neuer Favorit ab C # 6 ist $ "Konstanter Text hier {foo} und {bar}" ... es ist wie String.Formatbei Steroiden. Welche, Leistung klug, ist ein winzig bisschen langsamer an einem Liner als +und String.Concat, aber viel besser als die, wenn auch langsamer als StringBuilderbei mehreren Anrufen. In der Praxis sind die Leistungsunterschiede so groß, dass ich, wenn ich nur einen Weg zum Verketten wählen müsste, String-Interpolationen mit $... Wenn zwei Wege wählen würde , dann füge StringBuildersie meiner Toolbox hinzu. Mit diesen beiden Möglichkeiten sind Sie eingestellt.
U8it
Die folgende String.JoinAntwort wird nicht +gerecht und ist praktisch eine schlechte Methode, um Zeichenfolgen zu verketten, aber in Bezug auf die Leistung ist sie überraschend schnell. Die Antwort warum ist interessant. String.Concatund String.Joinkann beide auf Arrays wirken, ist aber String.Jointatsächlich schneller. Anscheinend String.Joinist es ziemlich ausgefeilt und optimierter als String.Concat, teilweise weil es ähnlich funktioniert, StringBuilderindem es zuerst die Stringlänge berechnet und dann den String erstellt, der von diesem Wissen profitiert, indem es UnSafeCharBuffer verwendet.
U8it
Ok, so schnell es ist, sondern String.Joinerfordert auch eine Reihe konstruieren , die Ressource ineffizient Recht scheint? ... Es stellte sich heraus , dass +und String.ConcatKonstrukt - Arrays für ihre Bestandteile sowieso. Folglich ist das manuelle Erstellen und Zuführen eines Arrays String.Joinvergleichsweise schneller. Es StringBuilderübertrifft jedoch String.Joinin nahezu jeder praktischen Hinsicht, während $es bei langen Zeichenfolgen nur geringfügig langsamer und viel schneller ist. Ganz zu schweigen davon, dass die Verwendung umständlich und hässlich ist, String.Joinwenn Sie dies getan haben um vor Ort ein Array dafür zu erstellen.
U8it

Antworten:

154

Die StringBuilder.Append()Methode ist viel besser als die Verwendung des +Operators. Aber ich habe festgestellt, dass es bei der Ausführung von 1000 Verkettungen oder weniger String.Join()noch effizienter ist als StringBuilder.

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

Das einzige Problem dabei String.Joinist, dass Sie die Zeichenfolgen mit einem gemeinsamen Trennzeichen verketten müssen.

Bearbeiten: Wie @ryanversaw betonte, können Sie das Trennzeichen festlegenstring.Empty .

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });
TheEmirOfGroofunkistan
quelle
11
StringBuilderhat enorme vergleichbare Startkosten und ist nur dann effizient, wenn es mit sehr großen Zeichenfolgen oder sehr vielen Verkettungen verwendet wird. Es ist nicht trivial, dies für eine bestimmte Situation herauszufinden. Wenn die Leistung ein Problem darstellt, ist die Profilerstellung Ihr Freund (siehe ANTS).
Abel
32
Dies gilt nicht für die Verkettung einzelner Zeilen. Angenommen, Sie machen myString = "foo" + var1 + "bar" + var2 + "hallo" + var3 + "world". Der Compiler verwandelt dies automatisch in einen string.concat-Aufruf, der so effizient wie möglich ist. Diese Antwort ist falsch, es gibt viele bessere Antworten zur Auswahl
csauve
2
Verwenden Sie für eine einfache String-Konzentration das, was am besten lesbar ist. Zeichenfolge a = b + c + d; wird fast immer schneller sein als mit StringBuilder, aber der Unterschied ist normalerweise irrelevant. Verwenden Sie StringBuilder (oder eine andere Option Ihrer Wahl), wenn Sie wiederholt zu derselben Zeichenfolge hinzufügen (z. B. einen Bericht erstellen) oder wenn Sie mit großen Zeichenfolgen arbeiten.
Swanny
5
Warum hast du nicht erwähnt string.Concat?
Venemo
271

Rico Mariani , der .NET Performance Guru, hatte einen Artikel zu diesem Thema. Es ist nicht so einfach, wie man vermuten könnte. Der grundlegende Rat lautet:

Wenn Ihr Muster so aussieht:

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

Das ist ein Concat und es ist flink, StringBuilder wird wahrscheinlich nicht helfen.

Wenn Ihr Muster so aussieht:

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

dann möchten Sie wahrscheinlich StringBuilder.

Ein weiterer Artikel zur Unterstützung dieser Behauptung stammt von Eric Lippert, in dem er die Optimierungen, die für +Verkettungen in einer Zeile durchgeführt wurden, ausführlich beschreibt.

Lee
quelle
1
Was ist mit String.Format ()?
IronSlug
85

Es gibt 6 Arten von Zeichenfolgenverkettungen:

  1. Verwenden Sie das Pluszeichen ( +).
  2. Verwenden von string.Concat().
  3. Verwenden von string.Join().
  4. Verwenden von string.Format().
  5. Verwenden von string.Append().
  6. Verwenden von StringBuilder.

In einem Experiment wurde dies bewiesen string.Concat() der beste Weg ist, wenn die Wörter weniger als 1000 (ungefähr) sind und wenn die Wörter mehr als 1000 sind, dann StringBuildersollte verwendet werden.

Weitere Informationen finden Sie auf dieser Website .

string.Join () vs string.Concat ()

Die string.Concat-Methode entspricht hier dem Aufruf der string.Join-Methode mit einem leeren Trennzeichen. Das Anhängen einer leeren Zeichenfolge ist schnell, aber nicht schneller. Daher wäre die Methode string.Concat hier überlegen.

Herr Green
quelle
4
Sollte lesen, wurde es als Zeichenfolge bewiesen. Concat () oder + ist der beste Weg. Ja, ich kann dies aus dem Artikel entnehmen, aber es erspart mir einen Klick. Also kompilieren + und concat in denselben Code.
brumScouse
Ich habe diese Basis verwendet, um eine meiner Methoden effizienter zu gestalten, bei der ich nur genau drei Zeichenfolgen verketten musste. Ich fand, dass +das tatsächlich 3 Millisekunden schneller war als string.Concat(), obwohl ich nicht die Anzahl der vor string.Concat()Outraces erforderlichen Zeichenfolgen untersucht habe +.
Gnemlock
59

Von Chinh Do - StringBuilder ist nicht immer schneller :

Faustregeln

  • Bei der Verkettung von dreiVerwenden Sie dynamischen Zeichenfolgenwerten oder weniger die herkömmliche Zeichenfolgenverkettung.

  • Verwenden Sie, wenn Sie mehr als drei dynamische Zeichenfolgenwerte verketten StringBuilder.

  • Verwenden Sie beim Erstellen einer großen Zeichenfolge aus mehreren Zeichenfolgenliteralen entweder das @Zeichenfolgenliteral oder den Inline + -Operator.

Die meiste Zeit StringBuilderist Ihre beste Wahl, aber es gibt Fälle, wie in diesem Beitrag gezeigt, in denen Sie zumindest über jede Situation nachdenken sollten.

Palehorse
quelle
8
afaik @ deaktiviert nur die Verarbeitung von Escape-Sequenzen. msdn.microsoft.com/en-us/library/362314fe.aspx zustimmen
abatishchev
12

Wenn Sie in einer Schleife arbeiten, StringBuilderist dies wahrscheinlich der richtige Weg. Dies erspart Ihnen den Aufwand, regelmäßig neue Zeichenfolgen zu erstellen. In Code, der nur einmal ausgeführt wird, String.Concatist dies wahrscheinlich in Ordnung.

Rico Mariani (.NET-Optimierungsguru) hat jedoch ein Quiz zusammengestellt, in dem er am Ende erklärte, dass er in den meisten Fällen empfiehlt String.Format.

Adam V.
quelle
Ich empfehle seit Jahren Leuten, mit denen ich gearbeitet habe, die Verwendung von string.format anstelle von string + string. Ich denke, die Lesbarkeitsvorteile sind ein zusätzlicher Vorteil, der über den Leistungsvorteil hinausgeht.
Scott Lawrence
1
Dies ist die richtige Antwort. Die derzeit akzeptierte Antwort für StringBuilder ist falsch, da keine einzeiligen Anhänge erwähnt werden, für die string.concat oder + schneller ist. Wenig bekannte Tatsache ist, dass der Compiler tatsächlich + in string.concat übersetzt. Außerdem verwende ich für Schleifen oder für Concats mit mehreren Zeilen einen benutzerdefinierten String-Builder, der nur angehängt wird, wenn .ToString aufgerufen wird - um das unbestimmte Pufferproblem zu überwinden, das StringBuilder hat
csauve
2
string.Format ist unter keinen Umständen der schnellste Weg. Ich weiß nicht, wie ich einen Fall erfinden soll, in dem er vor sich geht.
usr
@usr - Beachten Sie, dass Rico ausdrücklich nicht sagt , dass es das schnellste ist, sondern nur seine Empfehlung: "Obwohl es die schlechteste Leistung erbringt und wir dies bereits im Voraus wussten, stimmen beide CLR-Leistungsarchitekten darin überein, dass [string.Format] dies tun sollte Seien Sie die Standardauswahl. In dem höchst unwahrscheinlichen Fall, dass es zu einem Perfektionsproblem wird, kann das Problem mit nur bescheidenen lokalen Änderungen leicht behoben werden. Normalerweise profitieren Sie nur von einer guten Wartbarkeit. "
Adam V
@AdamV die Frage ist über den schnellsten Weg. Ich bin nicht der Meinung, dass dies die Standardauswahl ist, allerdings nicht aus perfekten Gründen. Es kann eine ungeschickte Syntax sein. Resharper kann nach Belieben hin und her konvertieren.
usr
10

Hier ist die schnellste Methode, die ich in einem Jahrzehnt für meine große NLP-App entwickelt habe. Ich habe Variationen für IEnumerable<T>und andere Eingabetypen mit und ohne Trennzeichen unterschiedlicher Typen ( Char, String), aber hier zeige ich den einfachen Fall, dass alle Zeichenfolgen in einem Array zu einer einzelnen Zeichenfolge ohne Trennzeichen verkettet werden . Die neueste Version hier wurde unter C # 7 und .NET 4.7 entwickelt und getestet .

Es gibt zwei Schlüssel für eine höhere Leistung. Die erste besteht darin, die genaue erforderliche Gesamtgröße vorab zu berechnen. Dieser Schritt ist trivial, wenn die Eingabe ein Array ist, wie hier gezeigt. Für die Behandlung IEnumerable<T>lohnt es sich, zuerst die Zeichenfolgen in einem temporären Array zu sammeln, um diese Summe zu berechnen (das Array ist erforderlich, um einen Aufruf zu vermeidenToString() mehr als einmal pro Element da dies technisch gesehen die Möglichkeit einer möglichen Änderung der erwarteten Semantik haben könnte einer 'String Join'-Operation).

Angesichts der Gesamtzuordnungsgröße der endgültigen Zeichenfolge wird der größte Leistungsschub erzielt, wenn die Ergebniszeichenfolge direkt erstellt wird . Dies erfordert die (möglicherweise umstrittene) Technik, die Unveränderlichkeit eines neuen, Stringder zunächst mit Nullen versehen ist, vorübergehend auszusetzen . Abgesehen von solchen Kontroversen ...

... beachten Sie, dass dies die einzige Massenverkettungslösung auf dieser Seite ist, die eine zusätzliche Zuordnungs- und Kopierrunde durch den StringKonstruktor vollständig vermeidet .

Vollständiger Code:

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

Ich sollte erwähnen, dass dieser Code eine geringfügige Änderung gegenüber dem aufweist, was ich selbst verwende. Im Original rufe ich die Anweisung cpblk IL von C # auf, um das eigentliche Kopieren durchzuführen . Zur Vereinfachung und Portabilität des Codes hier habe ich diesen memcpystattdessen durch P / Invoke ersetzt , wie Sie sehen können. Für höchste Leistung unter x64 ( aber möglicherweise nicht unter x86 ) möchten Sie möglicherweise stattdessen die cpblk- Methode verwenden.

Glenn Slayden
quelle
string.Joinerledigt all diese Dinge bereits für Sie. Sie müssen es nicht selbst schreiben. Es berechnet die Größe der endgültigen Zeichenfolge, erstellt eine Zeichenfolge dieser Größe und schreibt dann in das zugrunde liegende Zeichenarray. Es hat sogar den Vorteil, dass dabei lesbare Variablennamen verwendet werden.
Servy
1
@Servy Danke für den Kommentar; in der Tat String.Joinkann effizient sein. Wie ich im Intro angedeutet habe, ist der Code hier nur die einfachste Darstellung einer Funktionsfamilie, die ich für Szenarien verwende, die String.Joinentweder nicht (z. B. Optimierung für CharTrennzeichen) oder in früheren Versionen von .NET nicht verarbeitet wurden. Ich nehme an, ich hätte dies nicht für das einfachste Beispiel auswählen sollen, da es sich um einen Fall handelt, der String.Joinbereits gut funktioniert, wenn auch mit der "Ineffizienz", die wahrscheinlich nicht messbar ist, nämlich die Verarbeitung eines leeren Trennzeichens, nämlich. String.Empty.
Glenn Slayden
Sicher, in dem Fall , dass Sie nicht über einen Separator haben, dann sollten Sie nennen Concat, die auch dies richtig funktionieren. In beiden Fällen müssen Sie den Code nicht selbst schreiben.
Servy
7
@Servy Ich habe die Leistung von mit String.Joinmeinem Code unter Verwendung dieses Testkabels verglichen . Bei 10 Millionen zufälligen Verkettungsvorgängen mit jeweils bis zu 100 Zeichenfolgen in Wortgröße ist der oben gezeigte Code durchweg 34% schneller als String.Joinbei der Erstellung von x64- Versionen mit .NET 4.7 . Da das OP ausdrücklich die "effizienteste" Methode anfordert, deutet das Ergebnis darauf hin, dass meine Antwort zutrifft. Wenn dies Ihre Bedenken anspricht, lade ich Sie ein, Ihre Ablehnung zu überdenken.
Glenn Slayden
1
Ich habe dies kürzlich mit x64 full CLR 4.7.1 verglichen und festgestellt, dass es ungefähr doppelt so schnell wie string ist. Bei Verwendung von cpblk oder github.com/ wird ungefähr 25% weniger Speicher zugewiesen ( i.imgur.com/SxIpEmL.png ). JonHanna / Mnemosyne
Quentin-Starin
6

Aus diesem MSDN-Artikel :

Das Erstellen eines StringBuilder-Objekts ist zeitlich und im Speicher mit einem gewissen Aufwand verbunden. Auf einem Computer mit schnellem Speicher lohnt sich ein StringBuilder, wenn Sie ungefähr fünf Operationen ausführen. Als Faustregel würde ich sagen, dass 10 oder mehr Zeichenfolgenoperationen eine Rechtfertigung für den Overhead auf jeder Maschine sind, auch auf einer langsameren.

Wenn Sie also MSDN vertrauen, gehen Sie mit StringBuilder, wenn Sie mehr als 10 Zeichenfolgenoperationen / -verkettungen ausführen müssen - andernfalls ist eine einfache Zeichenfolgenübereinstimmung mit '+' in Ordnung.

JohnIdol
quelle
5

Beachten Sie, dass StringBuilder zusätzlich zu den anderen Antworten eine anfängliche Speichermenge zugewiesen werden kann .

Der Kapazitätsparameter definiert die maximale Anzahl von Zeichen, die in dem von der aktuellen Instanz zugewiesenen Speicher gespeichert werden können. Sein Wert wird der Capacity- Eigenschaft zugewiesen . Wenn die Anzahl der in der aktuellen Instanz zu speichernden Zeichen diesen Kapazitätswert überschreitet , weist das StringBuilder-Objekt zusätzlichen Speicher zum Speichern zu.

Wenn die Kapazität Null ist, wird die implementierungsspezifische Standardkapazität verwendet.

Das wiederholte Anhängen an einen nicht vorab zugewiesenen StringBuilder kann zu vielen unnötigen Zuweisungen führen, ebenso wie das wiederholte Verketten regulärer Zeichenfolgen.

Wenn Sie wissen, wie lang die endgültige Zeichenfolge sein wird, sie trivial berechnen oder eine fundierte Vermutung über den allgemeinen Fall anstellen können (zu viel zuzuweisen ist nicht unbedingt eine schlechte Sache), sollten Sie diese Informationen dem Konstruktor oder dem Kapazitätseigenschaft . Insbesondere beim Ausführen von Leistungstests, um StringBuilder mit anderen Methoden wie String.Concat zu vergleichen, die intern dasselbe tun. Jeder Test, den Sie online sehen und der keine StringBuilder-Vorzuweisung in seine Vergleiche einbezieht, ist falsch.

Wenn Sie keine Vermutung über die Größe anstellen können, schreiben Sie wahrscheinlich eine Dienstprogrammfunktion, die über ein eigenes optionales Argument zur Steuerung der Vorzuweisung verfügen sollte.

DBN
quelle
4

Das Folgende kann eine weitere alternative Lösung sein, um mehrere Zeichenfolgen zu verketten.

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

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

String-Interpolation

RP Nainwal
quelle
1
Dies ist eigentlich überraschend schön als allgemeine Methode der Verkettung. Es ist im Grunde String.Formataber besser lesbar und einfacher zu bearbeiten. Beim Benchmarking ist es etwas langsamer als +und String.Concatbei Verkettungen in einer Zeile, aber viel besser als bei wiederholten Anrufen, was StringBuilderweniger notwendig macht.
U8it
2

Am effizientesten ist es, StringBuilder wie folgt zu verwenden:

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

@jonezy: String.Concat ist in Ordnung, wenn Sie ein paar kleine Dinge haben. Wenn Sie jedoch Megabyte an Daten verketten, wird Ihr Programm wahrscheinlich tanken.

Der Schlumpf
quelle
Hey, was ist die Lösung für Megabyte Daten?
Neel
2

Probieren Sie diese 2 Codeteile aus und Sie werden die Lösung finden.

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

Sie werden feststellen, dass der erste Code sehr schnell endet und der Speicher in einer guten Menge vorhanden ist.

Der zweite Code ist vielleicht in Ordnung, aber es wird länger dauern ... viel länger. Wenn Sie also eine Anwendung für viele Benutzer haben und Geschwindigkeit benötigen, verwenden Sie die erste. Wenn Sie eine App für eine kurzfristige Ein-Benutzer-App haben, können Sie möglicherweise beide verwenden, oder die zweite ist für Entwickler "natürlicher".

Prost.

Eduardo Mass
quelle
1

Für nur zwei Zeichenfolgen möchten Sie StringBuilder definitiv nicht verwenden. Es gibt einen Schwellenwert, oberhalb dessen der StringBuilder-Overhead geringer ist als der Overhead beim Zuweisen mehrerer Strings.

Verwenden Sie für mehr als 2-3 Zeichenfolgen den Code von DannySmurf . Verwenden Sie andernfalls einfach den Operator +.

Nick
quelle
1

System.String ist unveränderlich. Wenn wir den Wert einer Zeichenfolgenvariablen ändern, wird dem neuen Wert ein neuer Speicher zugewiesen und die vorherige Speicherzuordnung freigegeben. System.StringBuilder wurde für das Konzept einer veränderlichen Zeichenfolge entwickelt, bei der eine Vielzahl von Operationen ausgeführt werden können, ohne dass der geänderten Zeichenfolge ein separater Speicherort zugewiesen werden muss.

Dhibi_Mohanned
quelle
1

Eine andere Lösung:

Verwenden Sie innerhalb der Schleife List anstelle von String.

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

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

es ist sehr sehr schnell.

Asady
quelle
0

Es würde vom Code abhängen. StringBuilder ist im Allgemeinen effizienter, aber wenn Sie nur ein paar Zeichenfolgen verketten und alles in einer Zeile erledigen, werden Code-Optimierungen dies wahrscheinlich für Sie erledigen. Es ist wichtig, darüber nachzudenken, wie der Code auch aussieht: Bei größeren Mengen erleichtert StringBuilder das Lesen, bei kleinen fügt StringBuilder nur unnötige Unordnung hinzu.

Jon Dewees
quelle