Javaの文字列結合について

第3回Effective Java読書会で項目51「文字列結合のパフォーマンスに用心する」がテーマに上がったのでそのあたりのお話を少しまとめておこうかと思います。

まず、Effective Javaの項目51には

  1. 文字列結合演算子は(+演算子)は便利だけど、使いどころを間違えるとパフォーマンスに影響を与える
  2. そんな場合はStringBuilderを使用する

ということがかかれています。


1に関して、どのような場合にパフォーマンスに影響を与えるかというと、主に以下のようなループ内で文字列を+演算子で結合する場合があげられると思います。

String result = "";
for( int i = 0; i < 10; i++ ){
    result += i;  //String結合
}


なぜこのような場合にパフォーマンスに影響を与えるかというと、文字列結合に+演算子を使用した場合にはコンパイル時にStringBuilderのappendメソッドを使用した処理に置き換えられ、実質的に以下のようなコードに変換されます。*1

String result = "";
for( int i = 0; i < 10; i++ ){
    StringBuilder sb = new StringBuilder( result );
    sb.append( i );
    result = sb.toString();
}

n回のループに対してn個のStringBuilderのインスタンスが生成されてしまうことになり、その分処理が遅くなってしまうのは明らかです。


このような場合には2に書かれているようにStringBuilderを使用した処理にしておくのが妥当です。これであればStringBuilderは最初に一つ作るだけで、無駄なStringBuilderのインスタンス生成処理でCPUやメモリ領域が使用されません。

StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10; i++ ){
    sb.append( i );
}
String result = sb.toString();


ちなみに、StringBuilderとよく似たクラスにStringBufferというクラスがあり、こちらは同期化処理を含んだ実装になっています。しかし、StringBuilder(StringBuffer)を複数のスレッド間で共有するということはまず無いと思うので、基本的にStringBuilderを使っておけば良いと思います。*2


ただし、文字列リテラル(と文字列定数)だけを結合する場合は+演算子を使用したうがパフォーマンスが良かったりします。例えば以下のようなコード

String text2 = "1234" + "5678";
}

であれば、コンパイル時にコンパイラがあらかじめ文字列結合した結果に変換してくれるため、実質的に以下のようなコードに変換されます。

String text2 = "12345678";
}

このような場合なら、わざわざ実行時にStringBuilderを使用して結合するよりかは、+演算子を使用してコンパイル時に結合してしまうほうがパフォーマンスが良いでしょう。


まとめ

  1. パフォーマンスが重要でない場合以外は、文字列の結合にStringBuilderを使用する
  2. 文字列リテラルだけを結合する場合は文字列結合演算子(+演算子)を使用する

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

*1:Javaのバージョンによっては挙動が異なるかもしれません。確認に使用したJavaのバージョンは1.6.0_24

*2:ちなみにStringBuilderはJava1.5から導入されたクラスなので、それ以前はStringBufferしかありません