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

## はじめに
- 結論から言うと、文字列結合に比べれば確かに遅い
- だが、書式文字列の置換を、文字列結合で代替するのは適切でない場合が多い。特に、パラメータ数が多い場合や、パラメータの文字列化が必要になる場合
- そもそも、「文字列中のプレースホルダを置換する」用途からすると、比べるべきは__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万回行い、平均値を算出する

```java
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__があり、__StringBuilder__と__String#format(String, Object...)__を組み合わせるより若干早い
 + これは__String#format(String, Object...)__が、呼び出し毎に内部で__Formatter__を生成する為と考えられる