感謝のプログラミング 10000時間

たどり着いた結果(さき)は、感謝でした。

なぜ文字列の連結は+演算子を使うより、StringBuilderのappend()メソッドを使った方がいいのか

スポンサーリンク

Stringを「+」で結合するのが望ましくない理由。

以下のブログで「Javaプログラマであるかを見分ける10の質問」という記事があった。
http://d.hatena.ne.jp/shuji_w6e/20110305/1299288660

その中で、「文字列の連結は原則として+演算子を使ってはならない理由を説明せよ。」という質問があったので、今日はそれについて考えてみたい(今更かよと言われそうですが、この記事を初めて知ったので・・・)

まず結論から言うと、Stringを「+」で結合していくのは遅いため、望ましくないと言える。

Stringオブジェクトは「不変」である

前提として、Stringクラスは文字列の内容を変更できない。
その辺は、StringクラスのコンストラクタJavadocを見てもそれっぽいことが書いてある。
「since Strings are immutable(Stringは不変だから).」

    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
    	//略
    }

では、「+」で結合する時にはどうやってStringオブジェクトを結合しているのだろうか?
その前に実際に比較してみる。

StringBuilderのappendとStringを+で結合する場合の比較

とりあえず、実際にサンプル的なのを作って、Stringを「+」で結合していく場合と、
StringBuilderのappendで結合していく場合を比較してみた。

package question;

public class StringSample {
    public static void main(String[] args) {
        String word1 = "hoge"; //結合していく文字列
        String stringResult;   //結果
        StringBuilder builderResult; //結果
        
        long start = System.currentTimeMillis();
        //Stringオブジェクトを「+」を使って、50000回分、「hoge」を付け足していく。
        stringResult = concatWord(50000, word1);
        long end = System.currentTimeMillis();
        System.out.println("Stringを+で結合してかかった時間:" + (end - start) + "ms");
        System.out.println("Stringを+で結合しましたが、その文字数は:" + stringResult.length());
        System.out.println("---------------------------------");
        
        //次に、StringBuilderのappendの結果を得る
        long start2 = System.currentTimeMillis();
        builderResult = concatByBuilder(50000, word1);
        long end2 = System.currentTimeMillis();
        System.out.println("StringBuilder appendで結合してかかった時間:" + (end2 - start2) + "ms");
        System.out.println("StringBuilderのappendで結合しましたが、その文字数は:" + builderResult.length());
        
        //文字列を比較するためにtoString()する
        String toCompareString = builderResult.toString();
        if (stringResult.equals(toCompareString)) {
            System.out.println("文字列は等しいみたいだね");
        } else {
            System.out.println("文字列が等しくないよ!");
        }
    }
    public static String concatWord(int loop, String concatWord) {
        String result = "";
        for (int i = 0; i < loop; i++) {
            result += concatWord;
        }
        return result;
    }
    public static StringBuilder concatByBuilder(int loop, String concatWord) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < loop; i++) {
            result.append(concatWord);
        }
        
        return result;
    }
}

結果はこうなる。

Stringを+で結合してかかった時間:15182ms
Stringを+で結合しましたが、その文字数は:200000
---------------------------------
StringBuilder appendで結合してかかった時間:7ms
StringBuilderのappendで結合しましたが、その文字数は:200000
文字列は等しいみたいだね

なんと2168倍もの差が出てしまった。

なぜこのように差が出てしまうのかと言うと、Stringオブジェクトを「+=」で結合していくときは、
内部でStringBuilderをわざわざ生成して、appendメソッドを呼び出して文字列を結合。
それをStringオブジェクトに戻して返す、という処理をやっているからだ。

いちいちStringBuilderオブジェクトを生成してappend処理をしている分、実行速度は遅くなってしまうということである。

結論

変更可能な文字列が欲しい場合は、StringBuilderオブジェクトを使おう。

ネタバレ

Javaプログラマを見分けるための質問」のような記事を見ると、勉強していたところがテストに出たときのような嬉しい気持ちになる。
なんでかというと、ああいう記事で書かれている質問の答えはほとんど全て、パーフェクトJavaに書かれているからだ。
Javaプログラマっぽいことをやり始めて1年半くらいになって、その間たくさんの本を読んできたんだけれど、パーフェクトJavaほど感動した本は無い。
もちろんというか、実は、このStringの結合の話もパーフェクトJavaの2章でとても詳しく解説してくれているのでした。

参考にした本

パーフェクトJava (PERFECT SERIES) (PERFECT SERIES 2)

パーフェクトJava (PERFECT SERIES) (PERFECT SERIES 2)

感謝のプログラミング

今回で感謝のプログラミングは【554時間目】
10000時間まで、あと【9446時間】