文字列データを結合するときにできるだけパフォーマンスが上がる方法を取りたい。StringとStringBuilderでどれぐらい違いが出るか確認した。ちなみにMacでローカル環境、Java21、Spring Boot3。

JUnitで簡易なテストコードを書く。testStringConcatenationPerformanceがString型で結合したテスト、testStringBuilderPerformanceがStringBuilderで結合したテスト。回数分”あいうえお”を結合した場合の速度をmsで出力した。まずは1万回。日本語ひらがなはUTF-8だと1文字3バイトなので15バイト×1万なので15万バイト。

package dev.itboot.mb;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class StringPerformanceTest {

    private static final int LOOP_COUNT = 10000;
    private static final String SAMPLE_TEXT = "あいうえお";

    @Test
    public void testStringConcatenationPerformance() {
        long start = System.currentTimeMillis();

        String result = "";
        for (int i = 0; i < LOOP_COUNT; i++) {
            result += SAMPLE_TEXT; // 文字列を追加
        }

        long end = System.currentTimeMillis();
        System.out.println("String concatenation time: " + (end - start) + "ms");
    }

    @Test
    public void testStringBuilderPerformance() {
        long start = System.currentTimeMillis();

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < LOOP_COUNT; i++) {
            result.append(SAMPLE_TEXT); // 文字列を追加
        }

        long end = System.currentTimeMillis();
        System.out.println("StringBuilder time: " + (end - start) + "ms");
    }
}

これがログだが、SrtingBuilderは0ms。1msもかかっていない。(たぶん早くて測定できない)一方、Stringの場合は61ms。差があるけどmsの世界だから実感が薄い。

次に回数を増やして10万回。

package dev.itboot.mb;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class StringPerformanceTest {

    private static final int LOOP_COUNT = 100000;
    private static final String SAMPLE_TEXT = "あいうえお";

    @Test
    public void testStringConcatenationPerformance() {
        long start = System.currentTimeMillis();

        String result = "";
        for (int i = 0; i < LOOP_COUNT; i++) {
            result += SAMPLE_TEXT; // 文字列を追加
        }

        long end = System.currentTimeMillis();
        System.out.println("String concatenation time: " + (end - start) + "ms");
    }

    @Test
    public void testStringBuilderPerformance() {
        long start = System.currentTimeMillis();

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < LOOP_COUNT; i++) {
            result.append(SAMPLE_TEXT); // 文字列を追加
        }

        long end = System.currentTimeMillis();
        System.out.println("StringBuilder time: " + (end - start) + "ms");
    }
}

SrtingBuilderは1ms、Stringは2282ms。22282msは2秒282ミリ秒。けっこうな差が出た。桁が3桁違う。

最後は100万回。

package dev.itboot.mb;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class StringPerformanceTest {

    private static final int LOOP_COUNT = 1000000;
    private static final String SAMPLE_TEXT = "あいうえお";

    @Test
    public void testStringConcatenationPerformance() {
        long start = System.currentTimeMillis();

        String result = "";
        for (int i = 0; i < LOOP_COUNT; i++) {
            result += SAMPLE_TEXT; // 文字列を追加
        }

        long end = System.currentTimeMillis();
        System.out.println("String concatenation time: " + (end - start) + "ms");
    }

    @Test
    public void testStringBuilderPerformance() {
        long start = System.currentTimeMillis();

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < LOOP_COUNT; i++) {
            result.append(SAMPLE_TEXT); // 文字列を追加
        }

        long end = System.currentTimeMillis();
        System.out.println("StringBuilder time: " + (end - start) + "ms");
    }
}

100万回になるとかなりの差。ローカル環境でやっているので自分のPCのメモリを使いまくってしまうので 100万回ループ実行中はPC操作もかなりモサモサしていた。SrtingBuilderは10ms、Stringは231038ms。231.038秒なので約 3分51秒。めちゃくちゃな差。

1万回、15万バイトぐらいは、大きな差はないけど、ループ回数を増やすほど差は顕著になるのでStringBuilderを使っておいた方がいい。

なぜStringの結合が遅いのか

なぜStringが遅いか理解しておきたい。以下の認識。

Stringは型とはいえ、クラス。そしてイミュータブルなのでループごとに新しいインスタンスが生成され、新しいメモリ領域が割り当てられるので生成と割り当てに時間がかかる。そして使用するメモリー領域が増えるのでサーバ処理が重くなる。Javaにはインスタンスを解放してくれるガベージコレクションがあるが、ループのインスタンス生成速度に間に合わないのではないかと思っている。(想像)。そしてガベージコレクションの動作によるオーバーヘッドも発生してしまう。以上からString結合はJavaであっても遅いのでStringBuilderで結合する。