web-dev-qa-db-ja.com

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

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

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

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

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

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

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

前もって感謝します!

編集:

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

88
Vaandu

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

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

目的は(通常)メモリを減らすことであることに注意してくださいchurn使用されるメモリの合計ではなく、ガベージコレクターでの作業を少し楽にするためです。

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

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

そのコードの作成者がStringBuilderを使用したい場合(引数がありますが、反対もあります。この回答の最後にある注を参照してください)、適切に実行することをお勧めします(ここでは、実際にid2の周りに引用符がないと仮定しています) table):

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

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

文字列の連結willがSun/Oracleコンパイラでコンパイルされている場合、カバーの下でStringBuilderを使用することを聞いたことがあるかもしれません。これは事実で、式全体に1つのStringBuilderを使用します。ただし、デフォルトのコンストラクターを使用するため、ほとんどの場合、再割り当てが必要になります。ただし、読みやすいです。これはnot trueのseries連結であることに注意してください。したがって、たとえば、これは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();

デフォルトのコンストラクタと後続の再割り当ては理想的ではありませんが、それで十分です—そして連結はlotより読みやすいです。

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

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;

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

ただし、ごく少数の場合を除いて重要ではないであり、特定のパフォーマンスの問題がない限り、読みやすさ(保守性を高める)が望ましいことを覚えておくことが重要です。

182
T.J. Crowder

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

これは良いでしょう:

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

この場合、文字列の連結は実際にはcompile-timeで行われているため、偶数単純なものと同等です:

return "select id1, id2 from table";

new StringBuilder().append("select id1, ").append(" id2 ")....toString()を使用すると、実際にはhinderのパフォーマンスが低下します。これは、連結を実行時間に強制的に実行するためですcompileの代わりに。おっとっと。

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

String/StringBufferの記事 があります。これは少し前に書いたもので、StringBuilderが登場する前です。ただし、原則はStringBuilderにも同じように適用されます。

38
Jon Skeet

[[ここにはいくつかの良い答えがありますが、まだ少しの情報が欠けていることがわかります。 ]]

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

あなたが指摘するように、あなたが与える例は単純化されていますが、とにかくそれを分析しましょう。ここで何が起こるかは、compiler+がすべて定数であるため、実際にここで"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;
}

最初の行の+は、1つの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();

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

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

10
Gray

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

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

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

4
Mike Nakis

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

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

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

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

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

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

2
Tomas Narros

MessageFormat も使用できます

1
JGFMK