SQLでStringBuilderを使用する正しい方法


88

私は私のプロジェクトで次のようなSQLクエリビルドを見つけました:

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

これStringBuilderはその目的、つまりメモリ使用量の削減を達成していますか?

コンストラクタでは '+'(文字列連結演算子)が使用されているので、疑問です。以下のコードのように文字列を使用するのと同じ量のメモリを消費しますか?sを使用しStringBuilder.append()た場合とは異なります。

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

両方のステートメントのメモリ使用量は同じですか?どうか明らかにしてください。

前もって感謝します!

編集:

ところで、それは私のコードではありません。古いプロジェクトで見つかりました。また、クエリは私の例のクエリほど小さくありません。:)


1
SQLのセキュリティ:常に使用するPreparedStatementか、または類似したもの:docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Christophe Roussy

メモリ使用量は別として、代わりにSQLビルダーライブラリを使用してみませんか。stackoverflow.com
q

回答:


182

StringBuilderの使用、つまりメモリの削減の目的。達成されましたか?

いいえ、まったくありません。そのコードはStringBuilder正しく使用されていません。(私はあなたがそれを間違って引用したと思います;確かに周りに引用符はid2ありtableませんか?)

(通常)目的は、使用される総メモリ量ではなくメモリチャーンを減らすことであり、ガベージコレクタでの動作を少し簡単にすることに注意してください。

それは以下のように文字列を使用するのと同等のメモリを消費しますか?

いいえ、引用した単なる連結よりも多くのメモリチャーンが発生します。(JVMオプティマイザーがStringBuilderコード内の明示が不要であると判断し、可能であればそれを最適化するまで。)

そのコードの著者が使用したい場合はStringBuilder(のため、だけでなく、賛否両論ありますが、この回答の最後にある注を参照)は、より良いそれを正しく行うには(ここで私は実際には存在しないと仮定しています前後に引用符id2table):

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

追加するコンテンツ全体に十分な容量で開始できるようsome_appropriate_sizeに、私はStringBuilderコンストラクターにリストしていることに注意してください。1を指定しない場合に使用されるデフォルトのサイズは16文字です。これは通常小さすぎて、StringBuilder再割り当てを行って自分自身を大きくする必要があります(IIRC、Sun / Oracle JDKでは、2倍になります[またはそれappendは部屋を使い果たすたびに特定の] を満たすためにもっと必要があることを知っています。

文字列連結StringBuilder、Sun / Oracleコンパイラーでコンパイルされた場合、裏で使用される聞いたことがあるかもしれません。これは本当です、それはStringBuilder全体的な表現のためにそれを使います。ただし、デフォルトのコンストラクタを使用するため、ほとんどの場合、再割り当てを行う必要があります。ただし、読みやすくなります。これがあることに注意してくださいではないの真の一連の連結の。たとえば、これは1つ使用しますStringBuilder

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

これはおおまかに次のように変換されます。

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

デフォルトコンストラクタとその後の再配分(s)は理想的ではないがそれは、大丈夫ですので、オッズは、それは良い十分ですしている-と連結はあるたくさんより読みやすいです。

しかし、それは単一の式に対してのみです。これには複数StringBuilderのが使用されます。

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

最終的には次のようになります。

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;

...これはかなり醜いです。

ただし、重要ではないことを覚えておくことが重要です。ごく少数の場合を除き、それは重要ではなく、特定のパフォーマンスの問題を除いて、可読性(保守性を向上させる)を優先することをお勧めします。


そうです。パラメーターなしのコンストラクターの使用は少し残念ですが、重要になることはほとんどありません。それが重大な問題になると疑う十分な理由x + y + zStringBuilderない限り、私はまだ単一の式を使用します。
Jon Skeet、2012年

@Crowderにはもう1つ疑問があります。StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");...。同様のsql.appendラインは約60ラインです。これは大丈夫ですか?
Vaandu 2012年

1
@Vanathi:(「疑問」ではなく「疑問」-一般的な誤訳です。)問題ありStringBuilderませんが、コンストラクタに渡した文字列と16文字に十分なスペースが最初に割り当てられるため、おそらく複数の再割り当てが発生します。そのため、16文字を超える文字を追加する場合(60文字の追加がある場合、あえてそうです!)、StringBuilder少なくとも1回、場合によっては何度も再割り当てする必要があります。最終結果がどのくらい大きくなるか(たとえば、400文字)の合理的なアイデアがある場合は、sql = new StringBuilder(400);(または何でも)appends を実行するのが最善です。
TJクラウダー2012年

@Vanathi:助けてくれてうれしいです。ええ、それが6,000文字になる場合StringBuilder、事前にメモリの再割り当てを約8節約できると伝えます(最初の文字列が約10文字であると仮定すると、SBは最初は26でしたが、52に倍増し、次に104、208、 416、832、1664、3328、最後に6656)。これがホットスポットである場合にのみ重要ですが、事前に知っている場合でも... :-)
TJ Crowder

@TJ Crowderは、パフォーマンスを向上させるために「+」演算子を使用してはならないことを意味しています。正しい?では、なぜOracalが "+"演算子を自分の言語で追加したのか、詳しく説明してもらえますか?
Smit Patel 2014

38

追加したいすべての「ピース」がすでにある場合は、まったく使用StringBuilderしても意味がありません。サンプルコードと同じ呼び出しで文字列連結を使用するStringBuilder と、さらに悪い結果になります。

これは良いでしょう:

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

この場合、文字列の連結はとにかく実際にコンパイル時に行われるため、さらに単純なものと同等です。

return "select id1, id2 from table";

使用しnew StringBuilder().append("select id1, ").append(" id2 ")....toString()ます実際に妨げることは連結がで実行されるように強制するので、この場合には、パフォーマンスを実行時間の代わりに、コンパイル時。おっとっと。

実際のコードがクエリにを含めることでSQLクエリを構築している場合、これは別の問題です。つまり、パラメーター化されたクエリを使用して、SQLではなくパラメーターに値を指定する必要があります。

私はしばらく前に書いた/ に関する記事StringStringBufferを持っています -来る前StringBuilderに。原則StringBuilderは同じように適用されます。


10

[[ここにはいくつかの良い答えがありますが、それでもまだ情報が不足していることがわかります。]]

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

あなたが指摘するように、あなたが与える例は単純化していますが、とにかくそれを分析しましょう。ここで発生するのは、すべて定数なので、コンパイラーが実際に+ここで機能すること"select id1, " + " id2 " + " from " + " table"です。したがって、これは次のようになります。

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

この場合、明らかに、を使用しても意味がありませんStringBuilder。あなたもするかもしれません:

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

ただし、フィールドやその他の非定数を追加する場合でも、コンパイラーは内部 を使用します。内部StringBuilderを定義する必要はありません。

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

内部的には、これは次のコードとほぼ同等のコードに変わります。

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

実際にStringBuilder 直接使用する必要があるのは、条件付きコードがある場合だけです。たとえば、次のようなコードはのために必死ですStringBuilder

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

+最初の行には、一の使用StringBuilderインスタンス。次に、+=は別のStringBuilderインスタンスを使用します。それを行うのがより効率的です:

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

私が使用するもう1つの時間StringBuilderは、いくつかのメソッド呼び出しから文字列を構築しているときです。次に、StringBuilder引数を取るメソッドを作成できます。

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

を使用しているときはStringBuilder、の使用を+同時に監視する必要があります。

sb.append("select " + fieldName);

これ+により、別の内部StringBuilderが作成されます。これはもちろん次のようになります。

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

最後に、@ TJrowderが指摘するように、常にのサイズを推測する必要がありStringBuilderます。これchar[]により、内部バッファーのサイズが大きくなるときに作成されるオブジェクトの数が節約されます。


4

あなたは、文字列ビルダーを使用する目的が少なくとも完全にではなく達成されていないと推測するのは正しいです。

ただし、コンパイラーが式"select id1, " + " id2 " + " from " + " table"を検出すると、実際StringBuilderに舞台裏を作成してそれに追加するコードを発行するため、最終的に結果はそれほど悪くありません。

しかし、もちろん、そのコードを見ている人は、それが一種の遅れていると考えるに違いありません。


2

あなたが投稿したコードでは、StringBuilderを誤用しているため、利点はありません。どちらの場合も同じ文字列を作成します。StringBuilderを+使用すると、appendメソッドを使用した文字列の操作を回避できます。次のように使用する必要があります。

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

Javaでは、String型は不変の文字シーケンスであるため、2つのStringを追加すると、VMは両方のオペランドを連結した新しいString値を作成します。

StringBuilderは可変の文字シーケンスを提供します。これを使用して、新しいStringオブジェクトを作成せずにさまざまな値または変数を連結できるため、文字列を操作するよりも効率的な場合があります。

これは、別のメソッド内でパラメーターとして渡される文字シーケンスの内容を変更するなど、いくつかの便利な機能を提供します。これは、ストリングでは実行できません。

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

詳細については、http://docs.oracle.com/javase/tutorial/java/data/buffers.htmlをご覧ください。


1
いいえ、すべきではありません。それは+とにかく同じコードに変換されるを使用するよりも読みにくくなります。StringBuilder1つの式ですべての連結を実行できない場合に役立ちますが、この場合はそうではありません。
Jon Skeet、2012年

1
質問の文字列が例として投稿されていることを理解しています。単一の定数 "select id1、id2 from table"
Tomas Narros

しかし、変数から非定数の値があったとしても、使用するStringBuilder場合は1つの値を使用しますreturn "select id1, " + foo + "something else" + bar;-なぜそうしないのですか?質問は、何かを渡す必要があることを示していませんStringBuilder
Jon Skeet、2012年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.