Javaで文字列(Stringクラスのインスタンス)を連結させる時は+演算子でもconcatでもなくStringBuilderやStringBufferクラスを使え!とよく言われます。その理由やStringBuilderクラス自身を理解するために、JDKのソースコードを読んでみました。*1その結果分かったことを、ここにまとめておきます。
隠されたnew
StringBuilderクラスのつくりは非常に簡単で、フィールドは文字列の実体であるcharと文字列の長さを表すintのみ。より古いJDKだと「charフィールドが外部から参照されているかどうか(toStringが実行されたかどうか)」を保持するbooleanを持っていたようですが、J2SE5.0ではtoStringが実行されるごとにStringクラスのインスタンスを新しく生成しているのでそうしたフィールドは必要ないようです。
このつくりは単純で理解しやすいのですが、toStringを実行するごとにStringクラスがインスタンス化されることに気をつけるべきでしょう。例えば下に挙げたサンプルはtoStringで取得した文字列をループで表示するだけのプログラムですが、toStringメソッドをループの外に出すだけでもかなり高速化しました。substringメソッドやtrimToSizeメソッドにも同じことが言えるでしょう。
StringBuilder sb = new StringBuilder("Hello"); // newは3回実行される for (int j = 0; j < 3; ++j) { System.out.println(sb.toString()); } // newは1回だけ実行される(実験環境ではおよそ4倍速!) String s = sb.toString(); for (int j = 0; j < 3; ++j) { System.out.println(s); }
他にも確保してある容量を超える文字列の追加(appendやinsert)を実行すると、内部で文字列容量の新規割り当てが発生します(新しいchar型の配列が作成されます)。引数なしのコンストラクタでは文字列長が16文字分しか確保されないので、結果が大きくなることがあらかじめ分かっているときはsetLengthメソッドやint型の引数を取るコンストラクタなどを利用して容量を充分に確保しないとメモリをかなり無駄にしてしまいそうです。
文字列長の限界
AbstractStringBuilderクラスのexpandCapacityメソッドを見る限りでは、文字列長の最大値はInteger.MAX_VALUEで決まるようです。Integer.MAX_VALUEは約21億ですので、これを超える文字列を扱うことはできません。
もちろんStringBuilderクラスでここまで長い文字列を扱うべきではありませんが、知っておいて損はなさそうです。
setLengthメソッドでは確保した容量は減らない
容量を減らすのはtrimToSizeメソッドであって、setLengthメソッド単体では割り当てられた容量は減りません。メモリ管理の効率化をそこまで意識することが多くあるかどうか分かりませんが、そうした使い方をする際は気をつける必要があるでしょう。
反面、toStringメソッドで得られる文字列の長さはsetLengthメソッドがきちんと反映されます。
StringBuilder sb = new StringBuilder("0123456789"); System.out.println(sb.capacity()); // => 26 System.out.println(sb.toString().length()); // => 10 sb.setLength(5); System.out.println(sb.capacity()); // => 26 System.out.println(sb.toString().length()); // => 5 sb.trimToSize(); System.out.println(sb.capacity()); // => 5 System.out.println(sb.toString().length()); // => 5
*1:今回は[http://java.sun.com/j2se/1.5.0/ja/download.html:title=J2SE 5.0 JDKのソースコード]を使用しました。このソースコードは[http://java.sun.com/j2se/1.5.0/scsl_5.0-license.txt:title=Sun Community Source License(SCSL)]の下で公開されています。ライセンス上問題がなければソースコードを引用したかったのですが、ライセンスすべてに目を通す時間を作るのは難しいため、今回の投稿では見送ります。ただ組織内のプロジェクトで生産用途に利用するわけでも製品を第三者へ配布するわけでもないので、おそらく大丈夫だとは思います。後日追記するかもしれません。