(注:このブログはもう更新していません)この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

[クラウド帳票エンジンDocurain]

Java17でString#formatが3倍速くなった

Redditで見かけたこちらの記事。

www.javaspecialists.eu

記事の内容を一言でまとめると

「これまでは常に正規表現でパースしていたが、簡単なパターン文字列は正規表現無しで処理するようにしたから速い」

なのですが、実際にどういうコードになったのか気になったので見てみました。

該当のコードは以下。

github.com

    private List<FormatString> parse(String s) {
        ArrayList<FormatString> al = new ArrayList<>();
        int i = 0;
        int max = s.length();
        Matcher m = null; // create if needed
        while (i < max) {
            int n = s.indexOf('%', i);
            if (n < 0) {
                // No more format specifiers, but since
                // i < max there's some trailing text
                al.add(new FixedString(s, i, max));
                break;
            }
            if (i != n) {
                // Previous characters were fixed text
                al.add(new FixedString(s, i, n));
            }
            i = n + 1; // ★ 次の文字を読む
            if (i >= max) {
                // Trailing %
                throw new UnknownFormatConversionException("%");
            }
            char c = s.charAt(i);
            if (Conversion.isValid(c)) { // ★ %の次の文字がパターン文字列だったらこれで終わり
                al.add(new FormatSpecifier(c));
                i++;
            } else { // ★ 簡単なパターン文字列でないなら従来通り正規表現
                if (m == null) {
                    m = fsPattern.matcher(s);
                }
                // We have already parsed a '%' at n, so we either have a
                // match or the specifier at n is invalid
                if (m.find(n) && m.start() == n) {
                    al.add(new FormatSpecifier(s, m));
                    i = m.end();
                } else {
                    throw new UnknownFormatConversionException(String.valueOf(c));
                }
            }
        }
        return al;
    }

「★」のところがポイントです。 %s とか %d のように、 %? のパターンだけ特別扱いしています。 %.3d とかはダメ。 大抵は単純なパターン文字列だけなので、結果的に「速い」と言うことですね。

Formatterクラスについて

あとは、Formatterクラス自体をキャッシュ可能にしてくれれば言うことはないのですが、、

何故そう言う実装なのか知りませんが、FormatterクラスはFormatter#format(String format, Object... args)を呼ぶ度に上記のパターンパースが走ります。java.util.regex.Patternみたいに、コンパイル済みのパターンを置いておける実装になってないんです。謎。