LoginSignup
10

More than 3 years have passed since last update.

String#format(String, Object...)は本当に遅いのか?

Last updated at Posted at 2017-04-11

はじめに

  • 結論から言うと、文字列結合に比べれば確かに遅い
  • だが、書式文字列の置換を、文字列結合で代替するのは適切でない場合が多い。特に、パラメータ数が多い場合や、パラメータの文字列化が必要になる場合
  • そもそも、「文字列中のプレースホルダを置換する」用途からすると、比べるべきはjava.text.MessageFormat#format(String, Object...)であり、StringBuilder#append(...)のメソッドチェーンではない
  • ということで、String#format(String, Object...)と、MessageFormat#format(String, Object...)の実行速度を比較してみる。(benchmark1)
  • ついでに、置換後文字列を保持する比較も行う。StringBuilder + String#format(String, Object...)と、java.util.Formatter#format(String, Object...)の比較 (benchmark2)

環境

バージョン
OS macOS 10.14.6
Java 1.8.0.202

ベンチマーク

  • 文字列2つの置換処理と、数値5つの置換処理を100万回行い、平均値を算出する
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

class FormatPerformance {
    /**
     * ループ回数(100万回)
     */
    private final int LOOP = 1000 * 1000;
    private static final ThreadLocalRandom random = ThreadLocalRandom.current();

    public static void main(String[] args) {
        FormatPerformance fp = new FormatPerformance();

        System.out.println("===== benchmark1 =====");
        fp.benchmark1();
        System.out.println("");
        System.out.println("===== benchmark2 =====");
        fp.benchmark2();
    }

    /**
     * ランダムな文字列を返します
     * 
     * @return ランダムなアルファベット文字列
     */
    private String randomString() {
        final int size = random.nextInt(16) + 1;
        StringBuilder sb = new StringBuilder(size);
        for (int i = 0; i < size; i++) {
            sb.append((char)('a' + random.nextInt(26)));
        }
        return sb.toString();
    }

    /**
     * 書式変換ベンチ<br>
     * {@link String#format(String, Object...)} vs {@link MessageFormat#format(String, Object...)}
     */
    private void benchmark1() {
        final String[][] expects = { { "すごーい! %sは%sが得意なフレンズなんだね!", "IPアドレス %d.%d.%d.%d/%d" },
                { "すごーい! {0}は{1}が得意なフレンズなんだね!", "IPアドレス {0}.{1}.{2}.{3}/{4}" }, };

        long totalByConcat = 0;
        long totalByString = 0;
        long totalByMessage = 0;
        for (int i = 0; i < LOOP; i++) {
            String paramStr1 = randomString();
            String paramStr2 = randomString();
            int paramInt1 = random.nextInt(255);
            int paramInt2 = random.nextInt(255);
            int paramInt3 = random.nextInt(255);
            int paramInt4 = random.nextInt(255);
            int paramInt5 = random.nextInt(32);
            List<String> list = new ArrayList<>();

            // 文字列結合
            {
                long begin = System.nanoTime();
                String sugoi = "すごーい! " + paramStr1 + "は" + paramStr2 + "が得意なフレンズなんだね!";
                String ipAddress = "IPアドレス " + Integer.toString(paramInt1) + "." + Integer.toString(paramInt2) + "."
                        + Integer.toString(paramInt3) + "." + Integer.toString(paramInt4) + "/"
                        + Integer.toString(paramInt5);
                totalByConcat += System.nanoTime() - begin;
                list.add(sugoi);
                list.add(ipAddress);
            }

            // String#format(String, Object...)
            {
                long begin = System.nanoTime();
                String sugoi = String.format(expects[0][0], paramStr1, paramStr2);
                String ipAddress = String.format(expects[0][1], paramInt1, paramInt2, paramInt3, paramInt4, paramInt5);
                totalByString += System.nanoTime() - begin;
                list.add(sugoi);
                list.add(ipAddress);
            }

            // MessageFormat#format(String, Object...)
            {
                long begin = System.nanoTime();
                String sugoi = MessageFormat.format(expects[1][0], paramStr1, paramStr2);
                String ipAddress = MessageFormat.format(expects[1][1], paramInt1, paramInt2, paramInt3, paramInt4, paramInt5);
                totalByMessage += System.nanoTime() - begin;
                list.add(sugoi);
                list.add(ipAddress);
            }
        }
        System.out.println(String.format("String concatenation average = %d[ns]", totalByConcat / LOOP));
        System.out.println(String.format("String#format(String, Object...) average = %d[ns]", totalByString / LOOP));
        System.out.println(String.format("MessageFormat#format(String, Object...) average = %d[ns]", totalByMessage / LOOP));
    }

    /**
     * 書式変換した文字列を蓄積するベンチ<br>
     * {@link String#format(String, Object...)} vs {@link Formatter#format(String, Object...)}
     */
    private void benchmark2() {
        final String[] expects = { "すごーい! %sは%sが得意なフレンズなんだね!", "IPアドレス %d.%d.%d.%d/%d" };

        long totalByString = 0;
        long totalByFormatter = 0;
        for (int i = 0; i < LOOP; i++) {
            String paramStr1 = randomString();
            String paramStr2 = randomString();
            int paramInt1 = random.nextInt(255);
            int paramInt2 = random.nextInt(255);
            int paramInt3 = random.nextInt(255);
            int paramInt4 = random.nextInt(255);
            int paramInt5 = random.nextInt(32);

            // StringBuilder + String#format
            {
                long begin = System.nanoTime();
                StringBuilder sb = new StringBuilder();
                sb.append(String.format(expects[0], paramStr1, paramStr2));
                sb.append(String.format(expects[1], paramInt1, paramInt2, paramInt3, paramInt4, paramInt5));
                totalByString += System.nanoTime() - begin;
            }

            // Formatter#format
            {
                long begin = System.nanoTime();
                try (Formatter formatter = new Formatter();) {
                    formatter.format(expects[0], paramStr1, paramStr2);
                    formatter.format(expects[1], paramInt1, paramInt2, paramInt3, paramInt4, paramInt5);
                }
                totalByFormatter += System.nanoTime() - begin;
            }
        }

        System.out.println(String.format("String#format(String, Object...) average = %d[ns]", totalByString / LOOP));
        System.out.println(String.format("Formatter#format(String, Object...) average = %d[ns]", totalByFormatter / LOOP));
    }
}

出力 (100万回あたりの平均)

===== benchmark1 =====
String concatenation average = 269[ns]
String#format(String, Object...) average = 2383[ns]
MessageFormat#format(String, Object...) average = 4572[ns]

===== benchmark2 =====
String#format(String, Object...) average = 2302[ns]
Formatter#format(String, Object...) average = 2101[ns]

結果

benchmark1

  • 文字列結合は、String#format(String, Object...)より10倍近く早い
  • しかし、String#format(String, Object...)も、MessageFormat#format(String, Object...)より4〜5割程度は早い
    • String#formatは、内部で正規表現を利用しているので遅いように思われるが、文字列をStringBuilderに保持している。対して、MessageFormat#formatは、StringBufferを用いている。また、カンマの付加等、単純な置換以上の処理ができる分重い

benchmark2

  • 書式文字列の置換後文字列を蓄積するような場合は、java.util.Formatterがあり、StringBuilderString#format(String, Object...)を組み合わせるより若干早い
    • これはString#format(String, Object...)が、呼び出し毎に内部でFormatterを生成する為と考えられる

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10