私のプロジェクトでこのようなSQLクエリを見つけました:
return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();
この StringBuilder
はその目的、すなわちメモリ使用量の削減を達成しているのでしょうか?
コンストラクタの中で '+' (String concat operator) が使われているからです。というのも、コンストラクタで '+' (String concat operator) が使われているからです。この場合、以下のコードのように String を使うのと同じ量のメモリを消費するのでしょうか?
return "select id1, " + " id2 " + " from " + " table";
どちらのステートメントもメモリ使用量は同じですか?明確にしてください。
よろしくお願いします!
編集
ところで、これは私のコードではありません。古いプロジェクトで見つけました。また、クエリは私の例のものほど小さくない。)
StringBuilderを使う目的、つまりメモリの削減。それは達成されていますか?
いいえ、まったく達成されていません。そのコードは StringBuilder
を正しく使っていません。(id2と
table`は引用符で囲まれていないはずだが。)
(通常)ガベージコレクタの負担を少しでも軽くするために、使用するメモリの総量よりもメモリの消費を減らすことが目的であることに注意してください。
以下のようにStringを使うのと同じようにメモリを使うのでしょうか?
いいえ、それは、あなたが引用したような単純な連結よりもより多くのメモリ消費を引き起こします。(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 文字][1] であるが、これは通常小さすぎて、
StringBuilderは自身を大きくするために再割り当てを行わなければならなくなる (IRC、Sun/Oracle JDK では、特定の
appendを満たすためにさらに必要であるとわかっている場合、
StringBuilder` は余裕がなくなるたびに自身を 2 倍にする [またはそれ以上にする])。
Sun/Oracleコンパイラでコンパイルされた場合、文字列の連結は隠れて StringBuilder
を使うと聞いたことがあるかもしれません。これは本当で、式全体に対して1つの 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();
デフォルトのコンストラクタとそれに続く再割り当ては理想的なものではありませんが、これで十分です。
しかし、それは1つの式の場合だけである。このために複数の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;
...これはかなり醜い。
しかし、ごく少数のケースを除けば、可読性は重要ではなく、特定のパフォーマンス上の問題がない限り、可読性(保守性を向上させる)を優先することが望ましいということを覚えておくことが重要です。
追加したい文字列がすでにすべて揃っている場合、StringBuilder
を使う意味はまったくありません。あなたのサンプルコードのように、StringBuilder
と文字列連結を同じ呼び出しで使用することはさらに悪いことです。
この方が良いだろう:
return "select id1, " + " id2 " + " from " + " table";
この場合、文字列の連結は実際にはコンパイル時に行われる:
return "select id1, id2 from table";
新しいStringBuilder().append("select id1, ").append(" id2 ")....toString()`を使用すると、コンパイル時ではなく、実行時に文字列の連結が行われるため、この場合実際には**パフォーマンスが低下します。おっと。
実際のコードがクエリに値を含めることでSQLクエリを構築しているのであれば、それはまた別の別の問題です。
StringBuilderが登場する少し前に書いたString
/ StringBuffer
に関する記事があります。しかし、この原則は StringBuilder
にも同じように当てはまります。
[[ここにはいくつかの良い答えがありますが、まだ少し情報が不足していることがわかります。 ]]。
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";
ただし、フィールドやその他の非定数を追加している場合でも、コンパイラはinternal 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
_directly_を使用する必要があるのは、条件付きコードがある場合だけです。 たとえば、次のように見えるコードは、「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
を使用するもう1つの時間は、いくつかのメソッド呼び出しから文字列を作成するときです。 次に、「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 []」オブジェクトの数が節約されます。
文字列ビルダーを使用する目的は、少なくともその完全な範囲では達成されていないと推測するのは正しいことです。
ただし、コンパイラーが「+」テーブルから「select id1」、「+」id2「+」という表現を見ると、実際にシーンの後ろに「StringBuilder」を作成して追加するコードが発せられるため、最終結果は結局それほど悪くはありません。
しかし、もちろん、そのコードを見ている人は誰でも、それは一種の遅延であると考えるに違いありません。
あなたが投稿したコードでは、StringBuilderの使い方を間違えているので、何のメリットもない。どちらの場合も同じStringをビルドすることになる。StringBuilderを使えば、append
メソッドを使った文字列の+
演算を避けることができる。
このように使うべきだ:
return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();
Javaでは、String型は不変の文字列なので、2つのStringを追加すると、VMは両方のオペランドを連結した新しいString値を作成します。
StringBuilderは、新しい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 を参照。