【Java】String, StringBuffer, StringBuilderの使い分け

Java初心者が見かける3つの文字列系のクラスについて使い分けや特徴を書いていく。
まず結論から書くと、

String

使いどころ:
  文字列を頻繁に変化(再代入、+演算子での連結など)させなくてよい場合
メリット:
  メモリの確保が最小限
デメリット:
  操作に対するパフォーマンスが極めて悪い(処理が遅い

StringBuffer

使いどころ:
  マルチスレッド環境下で、同じ文字列にアクセスして変更や結合などを頻繁に行う場合
メリット:
  Stringより文字列の操作が速い
デメリット:
  可変長であるため、初めに余分にメモリを確保する
  StringBuilderよりパフォーマンスが落ちる

StringBuilder

使いどころ:
  シングルスレッド環境下で、文字列の変更や結合などを頻繁に行う場合
メリット:
  Stringより文字列の操作が速い(最速!)
デメリット:
  可変長であるため、初めに余分にメモリを確保する
  マルチスレッド環境下では実行結果が保証されない

ということで3つを使い分けるといいらしい。
とはいえスレッドセーフで文字列を変更しなければならない場合なんてそうないらしく、
Stringか操作が一番早いStringBuilderを使うのが多いとのこと。


各クラスの詳細

ここからは、各クラスについてもう少し深く掘り下げてみる。

String

Stringは固定長でimmutable(不変)な文字列を扱うクラスである。
つまり、本来は文字列を再代入したり連結できないのだが、暗黙的に新しいインスタンスを生成1している。 新しいインスタンスの生成(new)は重い処理であるためパフォーマンスが低くなる。

package stringClasses;

public class StringTest {

    public static void main(String[] args) {
        String str = "俺はStringだ";
        
        // 再代入すると内部では、新しくインスタンスが生成される
        // str = new String("俺はStringなのか");
        str = "俺はStringなのか";
        
        // 結合も内部では、結合した後の文字列で
        // 新しくインスタンスを生成している
        // str = new String("俺はStringなのか?");
        str += "?";
    }

}

100回の連結にかかる時間も計測しておこう。

package stringClasses;

public class StringTime {

    public static void main(String[] args) {
        String str=new String();
        
        // 結合前の時刻を記録
        long startTime = System.nanoTime();
        
        for(int i = 0; i < 100; i++) {
            str += "!";
        }
        
        // 結合後の時刻を記録
        long endTime = System.nanoTime();
        
        // かかった時間を表示
        System.out.println(endTime - startTime);
    }

}
出力結果(ナノ秒): 113000

StringBufferとStringBuilder

StringBufferとStringBuilderの違いは、スレッドセーフか否かである。
実際に実装を確認してみても、大きな違いはsynchronized修飾子2があるかどうか。
最後に処理速度を比較しておこう。
まずはStringBufferから。

package stringClasses;

public class StringBufferTime {

    public static void main(String[] args) {
        StringBuffer strbf = new StringBuffer();

        // 結合前の時刻を記録
        long startTime = System.nanoTime();

        for (int i = 0; i < 100; i++) {
            strbf.append("!");
        }

        // 結合後の時刻を記録
        long endTime = System.nanoTime();

        // かかった時間を表示
        System.out.println(endTime - startTime);
    }

}
出力結果(ナノ秒): 31300

本当に記述方法が合ってるのか不安になるレベルで変わる。

次にStringBuilder。

package stringClasses;

public class StringBuilderTime {

    public static void main(String[] args) {
        StringBuilder strbl = new StringBuilder();

        // 結合前の時刻を記録
        long startTime = System.nanoTime();

        for (int i = 0; i < 100; i++) {
            strbl.append("!");
        }

        // 結合後の時刻を記録
        long endTime = System.nanoTime();

        // かかった時間を表示
        System.out.println(endTime - startTime);
    }

}
出力結果(ナノ秒): 29100

使い分け超大事だなと実感した。

【Java】+演算子、StringBuilder、StringBufferの違い(実際に測ってみた。)、文字列系の型 | プログラミングマガジン
【Java】StringBuilderとStringBufferの違いをスレッドセーフの観点で検証してみた - カタカタブログ


  1. Stringは全く同じ文字列であれば再利用される(新しいインスタンスは生成されない)
  2. 複数のスレッドが同時にメソッドを実行できないようにする