Sollte ich Javas String.format () verwenden, wenn die Leistung wichtig ist?


215

Wir müssen ständig Strings für die Protokollausgabe erstellen und so weiter. In den JDK-Versionen haben wir gelernt, wann StringBuffer(viele Anhänge, thread-sicher) und StringBuilder(viele Anhänge, nicht thread-sicher) zu verwenden sind.

Was ist der Rat zur Verwendung String.format()? Ist es effizient oder müssen wir uns an die Verkettung von Einzeilern halten, bei denen Leistung wichtig ist?

zB hässlicher alter Stil,

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

ordentlicher neuer Stil (String.format, der möglicherweise langsamer ist),

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

Hinweis: Mein spezieller Anwendungsfall sind die Hunderte von Einzeiler-Protokollzeichenfolgen in meinem Code. Sie beinhalten keine Schleife, sind also StringBuilderzu schwer. Ich interessiere mich String.format()speziell für.


28
Warum testest du es nicht?
Ed S.

1
Wenn Sie diese Ausgabe produzieren, muss sie für einen Menschen lesbar sein, so wie ein Mensch sie lesen kann. Sagen wir höchstens 10 Zeilen pro Sekunde. Ich denke, Sie werden feststellen, dass es wirklich egal ist, welchen Ansatz Sie wählen. Wenn er fiktiv langsamer ist, wird der Benutzer ihn möglicherweise zu schätzen wissen. ;) Also nein, StringBuilder ist in den meisten Situationen nicht schwergewichtig.
Peter Lawrey

9
@ Peter, nein, es ist absolut nicht zum Lesen in Echtzeit von Menschen! Es hilft bei der Analyse, wenn etwas schief geht. Die Protokollausgabe beträgt in der Regel Tausende von Zeilen pro Sekunde und muss daher effizient sein.
Air

5
Wenn Sie viele tausend Zeilen pro Sekunde produzieren, würde ich vorschlagen, 1) kürzeren Text zu verwenden, auch keinen Text wie CSV oder Binärtext. 2) String überhaupt nicht zu verwenden, Sie können die Daten in einen ByteBuffer schreiben, ohne sie zu erstellen Alle Objekte (als Text oder Binär) 3) Hintergrundinformationen zum Schreiben von Daten auf die Festplatte oder einen Socket. Sie sollten in der Lage sein, ungefähr 1 Million Leitungen pro Sekunde aufrechtzuerhalten. (Grundsätzlich so viel, wie Ihr Festplattensubsystem zulässt.) Sie können Bursts von 10x erreichen.
Peter Lawrey

7
Dies ist für den allgemeinen Fall nicht relevant, aber insbesondere für die Protokollierung verfügt LogBack (vom ursprünglichen Log4j-Autor geschrieben) über eine Form der parametrisierten Protokollierung, die genau dieses Problem behebt
Matt Passell

Antworten:


122

Ich habe eine kleine Klasse zum Testen geschrieben, die die bessere Leistung der beiden hat und + dem Format voraus ist. um den Faktor 5 bis 6. Probieren Sie es selbst aus

import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

Das Ausführen der obigen Schritte für verschiedene N zeigt, dass sich beide linear verhalten, jedoch String.format5 bis 30 Mal langsamer sind.

Der Grund ist, dass in der aktuellen Implementierung String.formatzuerst die Eingabe mit regulären Ausdrücken analysiert und dann die Parameter ausgefüllt werden. Die Verkettung mit Plus hingegen wird von javac (nicht von der JIT) optimiert und StringBuilder.appenddirekt verwendet.

Laufzeitvergleich


12
Ein Fehler bei diesem Test besteht darin, dass er nicht vollständig die gesamte Formatierung von Zeichenfolgen darstellt. Oft ist Logik enthalten, was eingeschlossen werden soll, und Logik, um bestimmte Werte in Zeichenfolgen zu formatieren. Jeder echte Test sollte sich mit realen Szenarien befassen.
Orion Adrian

9
Es gab eine andere Frage auf SO über + Verse StringBuffer, in neueren Versionen von Java + wurde wenn möglich durch StringBuffer ersetzt, damit die Leistung nicht anders sein würde
hhafez

25
Dies ähnelt stark der Art von Mikrobenchmark, die auf sehr ungewöhnliche Weise optimiert werden soll.
David H. Clements

20
Ein weiterer schlecht implementierter Mikro-Benchmark. Wie skalieren beide Methoden um Größenordnungen? Wie wäre es mit 100, 1000, 10000, 1000000 Operationen? Wenn Sie nur einen Test in einer Größenordnung für eine Anwendung ausführen, die nicht auf einem isolierten Kern ausgeführt wird. Es gibt keine Möglichkeit zu sagen, wie viel des Unterschieds aufgrund von Kontextwechsel, Hintergrundprozessen usw. als "Nebenwirkungen" abgeschrieben werden kann.
Evan Plaice

8
Da Sie nie aus dem Haupt-JIT herauskommen, können Sie nicht
einschalten

241

Ich nahm Hhafez- Code und fügte einen Gedächtnistest hinzu :

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

Ich führe dies separat für jeden Ansatz aus, den Operator '+', String.format und StringBuilder (Aufruf von toString ()), damit der verwendete Speicher nicht von anderen Ansätzen beeinflusst wird. Ich habe weitere Verkettungen hinzugefügt und die Zeichenfolge als "Blah" + i + "Blah" + i + "Blah" + i + "Blah" erstellt.

Das Ergebnis ist wie folgt (durchschnittlich jeweils 5 Läufe):
Annäherungszeit (ms)
Zugeordneter Speicher (lang) '+' Operator 747 320.504
String.format 16484 373.312
StringBuilder 769 57.344

Wir können sehen, dass String '+' und StringBuilder zeitlich praktisch identisch sind, aber StringBuilder ist bei der Speichernutzung viel effizienter. Dies ist sehr wichtig, wenn wir viele Protokollaufrufe (oder andere Anweisungen mit Zeichenfolgen) in einem Zeitintervall haben, das kurz genug ist, damit der Garbage Collector die vielen Zeichenfolgeninstanzen, die sich aus dem Operator '+' ergeben, nicht bereinigen kann.

Und eine Notiz, BTW, vergessen Sie nicht , die Protokollierung zu überprüfen Ebene , bevor die Nachricht zu konstruieren.

Schlussfolgerungen:

  1. Ich werde weiterhin StringBuilder verwenden.
  2. Ich habe zu viel Zeit oder zu wenig Leben.

8
"Vergessen Sie nicht, die Protokollierungsstufe zu überprüfen, bevor Sie die Nachricht erstellen", ist ein guter Rat. Dies sollte zumindest für Debug-Nachrichten erfolgen, da es viele davon geben kann und sie in der Produktion nicht aktiviert werden sollten.
Stivlo

39
Nein, das ist nicht richtig. Es tut mir leid, stumpf zu sein, aber die Anzahl der Stimmen, die es angezogen hat, ist geradezu alarmierend. Mit dem +Operator wird der entsprechende StringBuilderCode kompiliert . Mikrobenchmarks wie dieses sind keine gute Methode zur Messung der Leistung - warum nicht jvisualvm verwenden, es ist aus einem bestimmten Grund im JDK. String.format() wird langsamer sein, aber aufgrund der Zeit zum Analysieren der Formatzeichenfolge und nicht der Objektzuweisungen. Es ist ein guter Rat, die Erstellung von Protokollierungsartefakten zu verschieben, bis Sie sicher sind, dass sie benötigt werden. Wenn sich dies jedoch auf die Leistung auswirkt, ist dies am falschen Ort.
CurtainDog

1
@CurtainDog, Ihr Kommentar wurde zu einem vier Jahre alten Beitrag abgegeben. Können Sie auf die Dokumentation verweisen oder eine separate Antwort erstellen, um den Unterschied zu beheben?
Kurtzbot

1
Referenz zur Unterstützung des Kommentars von @ CurtainDog: stackoverflow.com/a/1532499/2872712 . Das heißt, + wird bevorzugt, es sei denn, es wird in einer Schleife ausgeführt.
Aprikose

And a note, BTW, don't forget to check the logging level before constructing the message.ist kein guter Rat. Angenommen, es handelt sich java.util.logging.*speziell um eine Überprüfung der Protokollierungsstufe, wenn Sie über eine erweiterte Verarbeitung sprechen, die nachteilige Auswirkungen auf ein Programm haben würde, die Sie nicht möchten, wenn für ein Programm die Protokollierung nicht auf der entsprechenden Ebene aktiviert ist. Die Formatierung von Zeichenfolgen ist überhaupt nicht diese Art der Verarbeitung. Die Formatierung ist Teil des java.util.loggingFrameworks, und der Protokollierer selbst überprüft die Protokollierungsstufe, bevor der Formatierer jemals aufgerufen wird.
Searchengine27

30

Alle hier vorgestellten Benchmarks weisen einige Mängel auf , daher sind die Ergebnisse nicht zuverlässig.

Ich war überrascht, dass niemand JMH für das Benchmarking verwendet hat, also habe ich es getan.

Ergebnisse:

Benchmark             Mode  Cnt     Score     Error  Units
MyBenchmark.testOld  thrpt   20  9645.834 ± 238.165  ops/s  // using +
MyBenchmark.testNew  thrpt   20   429.898 ±  10.551  ops/s  // using String.format

Einheiten sind Operationen pro Sekunde, je mehr desto besser. Benchmark-Quellcode . OpenJDK IcedTea 2.5.4 Java Virtual Machine wurde verwendet.

Der alte Stil (mit +) ist also viel schneller.


5
Dies wäre viel einfacher zu interpretieren, wenn Sie mit "+" und "Format" kommentieren würden.
AjahnCharles

21

Ihr alter hässlicher Stil wird von JAVAC 1.6 automatisch wie folgt kompiliert:

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

Es gibt also absolut keinen Unterschied zwischen diesem und der Verwendung eines StringBuilder.

String.format ist viel schwerer, da es einen neuen Formatierer erstellt, Ihren Eingabeformat-String analysiert, einen StringBuilder erstellt, alles daran anfügt und toString () aufruft.


In Bezug auf die Lesbarkeit ist der von Ihnen veröffentlichte Code viel ... umständlicher als String.format ("Was erhalten Sie, wenn Sie% d mit% d multiplizieren?", VarSix, varNine);
Dusktreader

12
Kein Unterschied zwischen +und in der StringBuilderTat. Leider gibt es in anderen Antworten in diesem Thread viele Fehlinformationen. Ich bin fast versucht, die Frage zu ändern how should I not be measuring performance.
CurtainDog

12

Das String.format von Java funktioniert folgendermaßen:

  1. Es analysiert die Formatzeichenfolge und explodiert in eine Liste von Formatblöcken
  2. Es iteriert die Formatblöcke und rendert in einen StringBuilder, bei dem es sich im Grunde um ein Array handelt, dessen Größe sich nach Bedarf ändert, indem es in ein neues Array kopiert wird. Dies ist notwendig, da wir noch nicht wissen, wie groß die endgültige Zeichenfolge sein soll
  3. StringBuilder.toString () kopiert seinen internen Puffer in einen neuen String

Wenn das endgültige Ziel für diese Daten ein Stream ist (z. B. Rendern einer Webseite oder Schreiben in eine Datei), können Sie die Formatblöcke direkt in Ihrem Stream zusammenstellen:

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

Ich spekuliere, dass der Optimierer die Formatzeichenfolgenverarbeitung optimieren wird. In diesem Fall bleibt Ihnen die gleiche amortisierte Leistung wie beim manuellen Abrollen Ihres String.Formats in einem StringBuilder.


5
Ich denke nicht, dass Ihre Spekulationen über die Optimierung der Formatzeichenfolgenverarbeitung richtig sind. In einigen realen Tests mit Java 7 stellte ich fest, dass die Verwendung String.formatin inneren Schleifen (millionenfach ausgeführt) zu mehr als 10% meiner Ausführungszeit führte java.util.Formatter.parse(String). Dies scheint darauf hinzudeuten, dass Sie in inneren Schleifen das Aufrufen Formatter.formatoder alles, was es aufruft, vermeiden sollten , einschließlich PrintStream.format(ein Fehler in Javas Standardbibliothek IMO, insbesondere da Sie die analysierte Formatzeichenfolge nicht zwischenspeichern können).
Andy MacKinlay

8

Um die erste Antwort oben zu erweitern / zu korrigieren, ist es keine Übersetzung, bei der String.format tatsächlich helfen würde.
String.format hilft beim Drucken eines Datums / einer Uhrzeit (oder eines numerischen Formats usw.), bei dem es Unterschiede in der Lokalisierung (l10n) gibt (dh einige Länder drucken den 04. Februar 2009 und andere den 04. Februar 2009).
Bei der Übersetzung geht es nur darum, externalisierbare Zeichenfolgen (wie Fehlermeldungen und was nicht) in ein Eigenschaftspaket zu verschieben, damit Sie mit ResourceBundle und MessageFormat das richtige Paket für die richtige Sprache verwenden können.

Wenn man sich all das oben Genannte ansieht, würde ich sagen, dass String.format vs. einfache Verkettung in Bezug auf die Leistung auf das hinausläuft, was Sie bevorzugen. Wenn Sie es vorziehen, Aufrufe von .format gegenüber Verkettung zu betrachten, sollten Sie dies auf jeden Fall tun.
Schließlich wird Code viel mehr gelesen als geschrieben.


1
Ich würde sagen, dass die Leistung von String.format im Vergleich zur einfachen Verkettung in Bezug auf die Leistung auf das zurückzuführen ist, was Sie bevorzugen. Ich denke, dies ist falsch. In Bezug auf die Leistung ist die Verkettung viel besser. Für weitere Details werfen Sie bitte einen Blick auf meine Antwort.
Adam Stelmaszczyk

6

In Ihrem Beispiel ist das Leistungsproblem nicht allzu unterschiedlich, es sind jedoch noch andere Aspekte zu berücksichtigen: die Speicherfragmentierung. Selbst bei einer Verkettungsoperation wird eine neue Zeichenfolge erstellt, auch wenn diese temporär ist (es dauert einige Zeit, sie zu analysieren, und es ist mehr Arbeit). String.format () ist nur besser lesbar und erfordert weniger Fragmentierung.

Wenn Sie häufig ein bestimmtes Format verwenden, vergessen Sie nicht, dass Sie die Formatter () -Klasse direkt verwenden können (String.format () instanziiert lediglich eine einmal verwendete Formatter-Instanz).

Außerdem sollten Sie noch etwas beachten: Achten Sie auf die Verwendung von Teilzeichenfolgen (). Beispielsweise:

String getSmallString() {
  String largeString = // load from file; say 2M in size
  return largeString.substring(100, 300);
}

Diese große Zeichenfolge befindet sich noch im Speicher, da Java-Teilzeichenfolgen genau so funktionieren. Eine bessere Version ist:

  return new String(largeString.substring(100, 300));

oder

  return String.format("%s", largeString.substring(100, 300));

Die zweite Form ist wahrscheinlich nützlicher, wenn Sie gleichzeitig andere Dinge tun.


8
Es ist erwähnenswert, dass die "verwandte Frage" tatsächlich C # ist und daher nicht anwendbar ist.
Air

Mit welchem ​​Tool haben Sie die Speicherfragmentierung gemessen und macht die Fragmentierung für RAM überhaupt einen Geschwindigkeitsunterschied?
kritzikratzi

Es sei darauf hingewiesen, dass die Teilstring-Methode von Java 7 + geändert wurde. Es sollte jetzt eine neue String-Darstellung zurückgeben, die nur die Teilzeichenfolgen enthält. Das bedeutet, dass es nicht erforderlich ist, einen Aufruf zurückzugeben. String :: new
João Rebelo

5

Im Allgemeinen sollten Sie String.Format verwenden, da es relativ schnell ist und die Globalisierung unterstützt (vorausgesetzt, Sie versuchen tatsächlich, etwas zu schreiben, das vom Benutzer gelesen wird). Dies erleichtert auch die Globalisierung, wenn Sie versuchen, eine Zeichenfolge gegenüber 3 oder mehr pro Anweisung zu übersetzen (insbesondere für Sprachen mit drastisch unterschiedlichen grammatikalischen Strukturen).

Wenn Sie nie vorhaben, etwas zu übersetzen, verlassen Sie sich entweder auf Javas integrierte Konvertierung von + -Operatoren in StringBuilder. Oder verwenden Sie Java StringBuilderexplizit.


3

Eine andere Perspektive nur aus Sicht der Protokollierung.

Ich sehe viele Diskussionen im Zusammenhang mit der Anmeldung in diesem Thread, daher dachte ich daran, meine Erfahrung als Antwort hinzuzufügen. Vielleicht findet es jemand nützlich.

Ich denke, die Motivation für die Protokollierung mit Formatierer liegt in der Vermeidung der Verkettung von Zeichenfolgen. Grundsätzlich möchten Sie keinen Overhead von String Concat haben, wenn Sie ihn nicht protokollieren möchten.

Sie müssen nicht wirklich concat / formatieren, es sei denn, Sie möchten sich anmelden. Sagen wir mal, ob ich eine solche Methode definiere

public void logDebug(String... args, Throwable t) {
    if(debugOn) {
       // call concat methods for all args
       //log the final debug message
    }
}

Bei diesem Ansatz wird der Cancat / Formatierer überhaupt nicht aufgerufen, wenn es sich um eine Debug-Nachricht handelt und debugOn = false

Es ist jedoch immer noch besser, hier StringBuilder anstelle des Formatierers zu verwenden. Die Hauptmotivation ist, dies zu vermeiden.

Gleichzeitig mag ich es nicht, seitdem für jede Protokollierungsanweisung einen "if" -Block hinzuzufügen

  • Dies beeinträchtigt die Lesbarkeit
  • Reduziert die Abdeckung meiner Komponententests - das ist verwirrend, wenn Sie sicherstellen möchten, dass jede Zeile getestet wird.

Daher ziehe ich es vor, eine Protokollierungsdienstprogrammklasse mit Methoden wie oben zu erstellen und sie überall zu verwenden, ohne mir Gedanken über Leistungseinbußen und andere damit verbundene Probleme zu machen.


Könnten Sie eine vorhandene Bibliothek wie slf4j-api nutzen, die vorgibt, diesen Anwendungsfall mit ihrer parametrisierten Protokollierungsfunktion anzugehen? slf4j.org/faq.html#logging_performance
Ammian

2

Ich habe gerade den Test von hhafez so geändert, dass er StringBuilder enthält. StringBuilder ist 33-mal schneller als String.format mit dem jdk 1.6.0_10-Client unter XP. Durch Verwendung des Schalters -server wird der Faktor auf 20 gesenkt.

public class StringTest {

   public static void main( String[] args ) {
      test();
      test();
   }

   private static void test() {
      int i = 0;
      long prev_time = System.currentTimeMillis();
      long time;

      for ( i = 0; i < 1000000; i++ ) {
         String s = "Blah" + i + "Blah";
      }
      time = System.currentTimeMillis() - prev_time;

      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         String s = String.format("Blah %d Blah", i);
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         new StringBuilder("Blah").append(i).append("Blah");
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);
   }
}

Obwohl dies drastisch klingen mag, halte ich es nur in seltenen Fällen für relevant, da die absoluten Zahlen ziemlich niedrig sind: 4 s für 1 Million einfache String.format-Aufrufe sind in Ordnung - solange ich sie für die Protokollierung oder das verwende mögen.

Update: Wie von sjbotha in den Kommentaren hervorgehoben, ist der StringBuilder-Test ungültig, da ihm ein Finale fehlt.toString() .

Der korrekte Beschleunigungsfaktor von String.format(.)bis StringBuilderist 23 auf meiner Maschine (16 mit dem -serverSchalter).


1
Ihr Test ist ungültig, da er die Zeit, die nur durch eine Schleife verbraucht wird, nicht berücksichtigt. Sie sollten dies einschließen und mindestens von allen anderen Ergebnissen abziehen (ja, es kann ein signifikanter Prozentsatz sein).
Cletus

Ich habe das gemacht, die for-Schleife dauert 0 ms. Aber selbst wenn es einige Zeit dauern würde, würde dies den Faktor nur erhöhen.
the.duckman

3
Der StringBuilder-Test ist ungültig, da er am Ende nicht toString () aufruft, um Ihnen tatsächlich einen String zu geben, den Sie verwenden können. Ich habe dies hinzugefügt und das Ergebnis ist, dass StringBuilder ungefähr genauso lange dauert wie +. Ich bin sicher, wenn Sie die Anzahl der Anhänge erhöhen, wird es irgendwann billiger.
Sarel Botha

1

Hier ist eine modifizierte Version des Hhafez-Eintrags. Es enthält eine String Builder-Option.

public class BLA
{
public static final String BLAH = "Blah ";
public static final String BLAH2 = " Blah";
public static final String BLAH3 = "Blah %d Blah";


public static void main(String[] args) {
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;
    int numLoops = 1000000;

    for( i = 0; i< numLoops; i++){
        String s = BLAH + i + BLAH2;
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        String s = String.format(BLAH3, i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        StringBuilder sb = new StringBuilder();
        sb.append(BLAH);
        sb.append(i);
        sb.append(BLAH2);
        String s = sb.toString();
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

}

}}

Zeit danach für Schleife 391 Zeit danach für Schleife 4163 Zeit danach für Schleife 227


0

Die Antwort darauf hängt stark davon ab, wie Ihr spezifischer Java-Compiler den von ihm generierten Bytecode optimiert. Zeichenfolgen sind unveränderlich und theoretisch kann jede "+" - Operation eine neue erstellen. Ihr Compiler optimiert jedoch mit ziemlicher Sicherheit Zwischenschritte beim Erstellen langer Zeichenfolgen. Es ist durchaus möglich, dass beide obigen Codezeilen genau denselben Bytecode generieren.

Die einzige Möglichkeit, dies zu wissen, besteht darin, den Code iterativ in Ihrer aktuellen Umgebung zu testen. Schreiben Sie eine QD-App, die Zeichenfolgen iterativ in beide Richtungen verkettet, und sehen Sie, wie sie gegeneinander ablaufen.


1
Der Bytecode für das zweite Beispiel ruft sicherlich String.format auf, aber ich wäre entsetzt, wenn eine einfache Verkettung dies tun würde. Warum sollte der Compiler eine Formatzeichenfolge verwenden, die dann analysiert werden müsste?
Jon Skeet

Ich habe "Bytecode" verwendet, wo ich "Binärcode" hätte sagen sollen. Wenn es um jmps und movs geht, kann es sich um genau denselben Code handeln.
Ja - dieser Jake.

0

Erwägen Sie die Verwendung "hello".concat( "world!" )einer kleinen Anzahl von Zeichenfolgen in der Verkettung. Es könnte für die Leistung sogar besser sein als andere Ansätze.

Wenn Sie mehr als 3 Zeichenfolgen haben, sollten Sie StringBuilder oder nur String verwenden, je nachdem, welchen Compiler Sie verwenden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.