Richtige Verwendung von StringBuilder in SQL

88

Ich habe gerade eine SQL-Abfrage wie diese in meinem Projekt gefunden:

return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();

Erreicht dies StringBuildersein Ziel, dh die Speichernutzung zu reduzieren?

Ich bezweifle das, weil im Konstruktor das '+' (String concat Operator) verwendet wird. Benötigt das die gleiche Menge an Speicher wie die Verwendung von String wie im folgenden Code? s Ich habe verstanden, es unterscheidet sich bei der Verwendung StringBuilder.append().

return "select id1, " + " id2 " + " from " + " table";

Sind beide Anweisungen in der Speichernutzung gleich oder nicht? Bitte klären Sie.

Danke im Voraus!

Bearbeiten:

Übrigens ist es nicht mein Code . Fand es in einem alten Projekt. Außerdem ist die Abfrage nicht so klein wie die in meinem Beispiel. :) :)

Vaandu
quelle
1
SQL-SICHERHEIT: Verwenden Sie immer PreparedStatementoder etwas Ähnliches: docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Christophe Roussy
Abgesehen von der Sache mit der Speichernutzung sollten Sie stattdessen eine SQL Builder-Bibliothek verwenden: stackoverflow.com/q/370818/521799
Lukas Eder

Antworten:

182

Das Ziel der Verwendung von StringBuilder, dh die Reduzierung des Speichers. Wird es erreicht?

Nein überhaupt nicht. Dieser Code wird nicht StringBuilderrichtig verwendet. (Ich denke, Sie haben es jedoch falsch zitiert; sicherlich gibt es keine Zitate id2und table?)

Beachten Sie, dass das Ziel ( in der Regel) ist Speicher zu reduzieren Churn anstatt Gesamtspeicher verwendet, um das Leben eines auf dem Garbage Collector zu erleichtern Bit.

Wird das gleich viel Speicherplatz beanspruchen wie unten?

Nein, es wird mehr Speicherabwanderung verursachen als nur das von Ihnen zitierte Straight Concat. (Bis / es sei denn, der JVM-Optimierer StringBuildererkennt, dass das Explizite im Code nicht erforderlich ist, und optimiert es, wenn dies möglich ist.)

Wenn der Autor dieses Codes verwenden möchte StringBuilder(es gibt Argumente dafür, aber auch dagegen; siehe Hinweis am Ende dieser Antwort), ist es besser, es richtig zu machen (hier gehe ich davon aus, dass es keine Anführungszeichen gibt id2und table):

StringBuilder sb = new StringBuilder(some_appropriate_size);
sb.append("select id1, ");
sb.append(id2);
sb.append(" from ");
sb.append(table);
return sb.toString();

Beachten Sie, dass ich some_appropriate_sizeim StringBuilderKonstruktor aufgelistet habe , damit er mit genügend Kapazität für den gesamten Inhalt beginnt, den wir anhängen werden. Die Standardgröße, die verwendet wird, wenn Sie keine angeben, beträgt 16 Zeichen.StringBuilder Dies ist normalerweise zu klein und führt dazu, dass Neuzuweisungen vorgenommen werden müssen, um sich selbst zu vergrößern (IIRC verdoppelt sich im Sun / Oracle-JDK selbst [oder mehr, wenn es weiß, dass es mehr braucht, um ein bestimmtes zu befriedigen append] jedes Mal, wenn es keinen Platz mehr hat).

Sie haben vielleicht gehört , dass die String - Verkettung wird eine Verwendung StringBuilderunter der Decke , wenn mit dem Sun / Oracle - Compiler kompiliert. Dies ist wahr, es wird eins StringBuilderfür den Gesamtausdruck verwendet. Es wird jedoch der Standardkonstruktor verwendet, was bedeutet, dass in den meisten Fällen eine Neuzuweisung erforderlich ist. Es ist jedoch einfacher zu lesen. Beachten Sie, dass dies für eine Reihe von Verkettungen nicht gilt . So wird zum Beispiel eins verwendet :StringBuilder

return "prefix " + variable1 + " middle " + variable2 + " end";

Es bedeutet grob:

StringBuilder tmp = new StringBuilder(); // Using default 16 character size
tmp.append("prefix ");
tmp.append(variable1);
tmp.append(" middle ");
tmp.append(variable2);
tmp.append(" end");
return tmp.toString();

Das ist in Ordnung, obwohl der Standardkonstruktor und die anschließende Neuzuweisung (en) nicht ideal sind, stehen die Chancen gut, dass sie gut genug sind - und die Verkettung ist viel besser lesbar.

Aber das ist nur für einen einzigen Ausdruck. Hierfür StringBuilderwerden mehrere s verwendet:

String s;
s = "prefix ";
s += variable1;
s += " middle ";
s += variable2;
s += " end";
return s;

Das wird so etwas:

String s;
StringBuilder tmp;
s = "prefix ";
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable1);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" middle ");
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable2);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" end");
s = tmp.toString();
return s;

... was ziemlich hässlich ist.

Es ist jedoch wichtig, sich daran zu erinnern, dass es bis auf wenige Fälle keine Rolle spielt und die Lesbarkeit (die die Wartbarkeit verbessert) bevorzugt wird, sofern kein bestimmtes Leistungsproblem vorliegt.

TJ Crowder
quelle
Richtig, das ist besser. Die Verwendung des parameterlosen Konstruktors ist etwas unglücklich, aber wahrscheinlich nicht signifikant. Ich würde immer noch einen einzelnen x + y + zAusdruck verwenden, es sei StringBuilderdenn, ich hätte guten Grund zu der Annahme, dass dies ein bedeutendes Problem sein würde.
Jon Skeet
@ Crowder haben noch einen Zweifel. StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");.... Ähnliche sql.appendZeilen sind ungefähr 60 Zeilen. Ist das in Ordnung?
Vaandu
1
@Vanathi: ("Frage", nicht "Zweifel" - es ist eine häufige Fehlübersetzung.) Es ist in Ordnung, führt aber wahrscheinlich zu mehreren Neuzuweisungen, da dem StringBuilderzunächst genügend Platz für die Zeichenfolge zugewiesen wird, die Sie dem Konstruktor übergeben haben, plus 16 Zeichen. Wenn Sie also mehr als 16 Zeichen anhängen (ich glaube, Sie sind es, wenn es 60 Anhänge gibt!), Muss das StringBuildermindestens einmal und möglicherweise viele Male neu zugewiesen werden. Wenn Sie eine vernünftige Vorstellung davon haben, wie groß das Endergebnis sein wird (z. B. 400 Zeichen), tun Sie am besten sql = new StringBuilder(400);(oder was auch immer) das appends.
TJ Crowder
@ Vanathi: Schön, dass das geholfen hat. Ja, wenn es 6.000 Zeichen sein sollen, bedeutet dies, StringBuilderdass im Voraus etwa acht Speicherzuordnungen eingespart werden (vorausgesetzt, die anfängliche Zeichenfolge besteht aus etwa 10 Zeichen, wäre der SB zunächst 26 gewesen und dann auf 52, dann auf 104, 208 verdoppelt worden). 416, 832, 1664, 3328 und schließlich 6656). Nur wichtig, wenn dies ein Hotspot ist, aber dennoch, wenn Sie im Voraus wissen ... :-)
TJ Crowder
@TJ Crowder Sie wollen sagen, ich darf den Operator "+" nicht für eine bessere Leistung verwenden. richtig? Warum hat Oracal dann den Operator "+" in seiner Sprache hinzugefügt? Können Sie das bitte näher erläutern?
Smit Patel
38

Wenn Sie bereits alle "Teile" haben, die Sie anhängen möchten, macht es keinen Sinn, sie StringBuilderüberhaupt zu verwenden. Die Verwendung StringBuilder und Verkettung von Zeichenfolgen im selben Aufruf wie in Ihrem Beispielcode ist noch schlimmer.

Das wäre besser:

return "select id1, " + " id2 " + " from " + " table";

In diesem Fall findet die Verkettung der Zeichenfolgen ohnehin zur Kompilierungszeit statt , sodass sie dem noch einfacheren entspricht:

return "select id1, id2 from table";

Mit Hilfe new StringBuilder().append("select id1, ").append(" id2 ")....toString()tatsächlich behindern Leistung in diesem Fall, da es die Verkettung an durchgeführt wird , zwingt die Ausführungszeit , statt bei der Kompilierung Zeit. Hoppla.

Wenn der reale Code eine SQL-Abfrage erstellt, indem Werte in die Abfrage aufgenommen werden, ist dies ein weiteres separates Problem: Sie sollten parametrisierte Abfragen verwenden und die Werte in den Parametern und nicht in SQL angeben.

Ich habe einen Artikel über String/StringBuffer den ich vor einiger Zeit geschrieben habe - bevor ich mitgekommen bin StringBuilder. Die Grundsätze gelten jedoch StringBuilderin gleicher Weise.

Jon Skeet
quelle
10

[[Hier gibt es einige gute Antworten, aber ich finde, dass ihnen noch ein paar Informationen fehlen. ]]

return (new StringBuilder("select id1, " + " id2 " + " from " + " table"))
     .toString();

Wie Sie hervorheben, ist das Beispiel, das Sie geben, vereinfachend, aber lassen Sie es uns trotzdem analysieren. Was hier passiert, ist, dass der Compiler die +Arbeit hier tatsächlich erledigt , weil "select id1, " + " id2 " + " from " + " table"alle Konstanten sind. Das wird also zu:

return new StringBuilder("select id1,  id2  from  table").toString();

In diesem Fall macht es offensichtlich keinen Sinn, sie zu verwenden StringBuilder. Sie könnten genauso gut tun:

// the compiler combines these constant strings
return "select id1, " + " id2 " + " from " + " table";

Selbst wenn Sie Felder oder andere Nichtkonstanten anhängen würden, würde der Compiler ein internes verwenden StringBuilder - Sie müssen keines definieren:

// an internal StringBuilder is used here
return "select id1, " + fieldName + " from " + tableName;

Unter der Decke wird daraus Code, der ungefähr gleichbedeutend ist mit:

StringBuilder sb = new StringBuilder("select id1, ");
sb.append(fieldName).append(" from ").append(tableName);
return sb.toString();

Wirklich das einzige Mal, dass Sie StringBuilder direkt verwenden müssen, ist, wenn Sie bedingten Code haben. Zum Beispiel ist Code, der wie folgt aussieht, verzweifelt nach StringBuilder:

// 1 StringBuilder used in this line
String query = "select id1, " + fieldName + " from " + tableName;
if (where != null) {
   // another StringBuilder used here
   query += ' ' + where;
}

Das +in der ersten Zeile verwendet eine StringBuilderInstanz. Dann verwendet der +=eine andere StringBuilderInstanz. Es ist effizienter zu tun:

// choose a good starting size to lower chances of reallocation
StringBuilder sb = new StringBuilder(64);
sb.append("select id1, ").append(fieldName).append(" from ").append(tableName);
// conditional code
if (where != null) {
   sb.append(' ').append(where);
}
return sb.toString();

Ein anderes Mal, StringBuilderwenn ich a verwende, erstelle ich eine Zeichenfolge aus einer Reihe von Methodenaufrufen. Dann kann ich Methoden erstellen, die ein StringBuilderArgument annehmen :

private void addWhere(StringBuilder sb) {
   if (where != null) {
      sb.append(' ').append(where);
   }
}

Wenn Sie a verwenden StringBuilder, sollten Sie +gleichzeitig auf die Verwendung von achten:

sb.append("select " + fieldName);

Dadurch +wird ein weiteres internes StringBuildererstellt. Dies sollte natürlich sein:

sb.append("select ").append(fieldName);

Schließlich sollten Sie, wie @TJrowder hervorhebt, immer die Größe des erraten StringBuilder. Dies spart die Anzahl der char[]Objekte, die beim Vergrößern des internen Puffers erstellt werden.

Grau
quelle
4

Sie vermuten zu Recht, dass das Ziel der Verwendung von String Builder nicht erreicht wird, zumindest nicht in vollem Umfang.

Wenn der Compiler jedoch den Ausdruck sieht "select id1, " + " id2 " + " from " + " table", gibt er Code aus, der tatsächlich einen StringBuilderBlick hinter die Kulissen wirft und an diesen anhängt, sodass das Endergebnis nicht so schlecht ist.

Aber natürlich muss jeder, der sich diesen Code ansieht, denken, dass er irgendwie verzögert ist.

Mike Nakis
quelle
2

In dem Code, den Sie gepostet haben, gibt es keine Vorteile, da Sie den StringBuilder missbrauchen. In beiden Fällen erstellen Sie denselben String. Mit StringBuilder können Sie die +Operation für Strings mithilfe der appendMethode vermeiden . Sie sollten es so verwenden:

return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();

In Java ist der String-Typ eine unveränderliche Folge von Zeichen. Wenn Sie also zwei Strings hinzufügen, erstellt die VM einen neuen String-Wert, wobei beide Operanden verkettet sind.

StringBuilder bietet eine veränderbare Folge von Zeichen, mit der Sie verschiedene Werte oder Variablen zusammenfassen können, ohne neue String-Objekte zu erstellen. Daher ist StringBuilder manchmal effizienter als das Arbeiten mit Strings

Dies bietet einige nützliche Funktionen, z. B. das Ändern des Inhalts einer Zeichenfolge, die als Parameter in einer anderen Methode übergeben wird, was mit Strings nicht möglich ist.

private void addWhereClause(StringBuilder sql, String column, String value) {
   //WARNING: only as an example, never append directly a value to a SQL String, or you'll be exposed to SQL Injection
   sql.append(" where ").append(column).append(" = ").append(value);
}

Weitere Informationen finden Sie unter http://docs.oracle.com/javase/tutorial/java/data/buffers.html

Tomas Narros
quelle
1
Nein, das solltest du nicht. Es ist weniger lesbar als die Verwendung +, die ohnehin in denselben Code konvertiert wird. StringBuilderist nützlich, wenn Sie nicht die gesamte Verkettung in einem einzelnen Ausdruck ausführen können, in diesem Fall jedoch nicht.
Jon Skeet
1
Ich verstehe, dass die Zeichenfolge in der Frage als Beispiel angegeben ist. Es würde keinen Sinn machen, einen "festen" String wie diesen weder mit StringBuilder zu erstellen noch verschiedene Fragmente hinzuzufügen, da man ihn einfach in einer einzigen Konstante definieren könnte "select id1, id2 from table"
Tomas Narros
Aber selbst wenn es nicht konstante Werte von Variablen gäbe, würde es immer noch einen einzigen verwenden, StringBuilderwenn Sie verwenden würden return "select id1, " + foo + "something else" + bar;- warum also nicht? Die Frage gibt keinen Hinweis darauf, dass irgendetwas weitergegeben werden muss StringBuilder.
Jon Skeet
1

Sie können auch verwenden Message zu

JGFMK
quelle