Java Tips | 文字列処理:高速StringBuilder

Java Java
スポンサーリンク

「高速StringBuilder」は“たくさんつなぐ”場面を丸ごと任せる道具

前回の「文字列連結」で触れたとおり、+ 連結は少ない回数なら問題ありませんが、
ループの中で何百回・何千回と連結するときは、StringBuilder を使うほうが圧倒的に有利です。

ここで一歩進めて、「業務でよくある“たくさんつなぐ処理”を、毎回きれいに・速く書けるようにするためのラッパー」として
“高速StringBuilderユーティリティ”を用意しておく、という発想を持ってほしいんです。

「速いこと」だけが目的ではなく、「書きやすさ」「バグりにくさ」「ルールの一元化」も含めて、
“高速に文字列を組み立てるための型”を作るイメージです。


なぜ「ただの StringBuilder」では足りなくなるのか

速度だけでなく「書き味」と「ルール」が欲しくなる

素の StringBuilder はこう使います。

StringBuilder sb = new StringBuilder();
sb.append("userId=");
sb.append(userId);
sb.append(", action=");
sb.append(action);
String log = sb.toString();
Java

これでも十分速いのですが、業務コードに大量に出てくると、次のようなモヤモヤが出てきます。

どこでも new StringBuilder() していて、初期容量がバラバラ。
区切り文字(,\n)の付け方が人によって違う。
null の扱いがバラバラ(”null” と出したり、空文字にしたり)。

つまり、「パフォーマンスは良いけど、書き方とルールが揃わない」状態になりがちです。
ここを“ユーティリティ化した StringBuilder”で整えていきます。


基本形:業務用の「FastStringBuilder」を定義する

append のラップと、toString のシンプル化

まずは、StringBuilder をそのまま包んだシンプルなクラスから始めます。

public final class FastStringBuilder {

    private final StringBuilder sb;

    public FastStringBuilder() {
        this.sb = new StringBuilder();
    }

    public FastStringBuilder(int capacity) {
        this.sb = new StringBuilder(capacity);
    }

    public FastStringBuilder append(Object value) {
        sb.append(String.valueOf(value));
        return this;
    }

    @Override
    public String toString() {
        return sb.toString();
    }
}
Java

使い方はこうです。

FastStringBuilder fsb = new FastStringBuilder();
String log = fsb
        .append("userId=").append(userId)
        .append(", action=").append(action)
        .append(", result=").append(result)
        .toString();

System.out.println(log);
Java

ここでの重要ポイントは、「append が Object を受け取り、メソッドチェーンできる」ことです。
String.valueOf を中に閉じ込めているので、null を渡しても "null" という文字列になりますし、
呼び出し側は append をつなげていくだけで、+ 連結よりも見通しの良いコードになります。

まだこれだけだと「ただのラッパー」ですが、ここに“業務でよく使うパターン”を足していくと、一気に便利になります。


区切り文字付きの append を組み込む

「最初だけ区切りを付けない」を自動化する

よくあるのが、「カンマ区切りで値を並べたいけど、先頭にはカンマを付けたくない」というパターンです。
これを毎回 if で書くのはダルいので、FastStringBuilder に覚えさせます。

public final class FastStringBuilder {

    private final StringBuilder sb;
    private boolean first = true;

    public FastStringBuilder() {
        this.sb = new StringBuilder();
    }

    public FastStringBuilder(int capacity) {
        this.sb = new StringBuilder(capacity);
    }

    public FastStringBuilder append(Object value) {
        sb.append(String.valueOf(value));
        return this;
    }

    public FastStringBuilder appendWithDelimiter(String delimiter, Object value) {
        if (!first) {
            sb.append(delimiter);
        } else {
            first = false;
        }
        sb.append(String.valueOf(value));
        return this;
    }

    @Override
    public String toString() {
        return sb.toString();
    }
}
Java

使い方はこうです。

FastStringBuilder fsb = new FastStringBuilder();
String csvLine = fsb
        .appendWithDelimiter(",", "山田太郎")
        .appendWithDelimiter(",", 30)
        .appendWithDelimiter(",", "東京")
        .toString();

System.out.println(csvLine); // 山田太郎,30,東京
Java

ここで深掘りしたいのは、「“最初だけ区切りを付けない”というよくあるロジックを、ビルダー側に閉じ込めている」ことです。
呼び出し側は「区切り文字」と「値」だけを渡せばよく、
if (i > 0) { sb.append(","); } のようなコードを二度と書かなくて済みます。

このパターンは CSV、ログ、SQL の IN 句、メッセージの列挙など、業務のあらゆる場面で使い回せます。


例題:ログメッセージを高速かつ読みやすく組み立てる

「キー=値」を高速に並べる

先ほどの LogStrings.kv と組み合わせると、ログ用の高速ビルダーが作れます。

public final class LogBuilder {

    private final FastStringBuilder fsb = new FastStringBuilder();

    public LogBuilder kv(String key, Object value) {
        fsb.appendWithDelimiter(", ", key + "=" + String.valueOf(value));
        return this;
    }

    public String build() {
        return fsb.toString();
    }
}
Java

使い方はこうです。

LogBuilder lb = new LogBuilder();
String log = lb
        .kv("userId", userId)
        .kv("action", action)
        .kv("result", result)
        .build();

System.out.println(log);
// userId=u-001, action=LOGIN, result=SUCCESS
Java

ここでのポイントは、「“キー=値をカンマ区切りで並べる”というログの型を、クラスとして表現している」ことです。
内部では FastStringBuilder が効率よく連結してくれているので、
ループで大量にログを組み立てるような場面でも、パフォーマンスと可読性を両立できます。


初期容量を意識して「本当に速くする」

だいたいの長さが分かるなら、最初に教えてあげる

StringBuilderFastStringBuilder の速度をもう一段上げたいときに効くのが、「初期容量の指定」です。

FastStringBuilder fsb = new FastStringBuilder(256);
Java

こうしておくと、内部バッファが最初から 256 文字分確保されるので、
それを超えない限り、バッファの再確保(拡張)が発生しません。

例えば、「1行あたりだいたい 100〜200 文字くらいのログを 1万行作る」と分かっているなら、
FastStringBuilder(256) のように少し余裕を持った容量を指定しておくと、
GC の負荷やメモリアロケーションの回数を減らせます。

ここで深掘りしたいのは、「“速くする”とは、アルゴリズムだけでなく“無駄な再確保を減らす”ことでもある」という感覚です。
毎回 new StringBuilder() しているだけではなく、「この用途ならこのくらいの容量」といった“経験値”をコードに刻んでいくと、
業務システムとしての安定感が一段上がります。


まとめ:高速StringBuilderユーティリティで身につけたい感覚

「高速StringBuilder」は、単に StringBuilder をラップしたクラスではなく、
「たくさん文字列をつなぐ処理を、速く・きれいに・同じルールで書くための“型”」だと捉えてほしいです。

押さえておきたい感覚は、まず「ループや大量連結では、+ ではなくビルダーを使う」こと。
次に、「区切り文字付き append や、キー=値の並びなど、“よく出る連結パターン”をビルダーに覚えさせる」こと。
そして、「用途ごとに初期容量やルールを決めたビルダーを用意し、プロジェクト全体で同じ“書き味”と“速度”を共有する」ことです。

もしあなたのコードのどこかに、s = s + part; のようなループ内連結や、
if (i > 0) sb.append(","); が何度も出てくる箇所があれば、
そこを題材にして、ここで作ったような FastStringBuilder ベースの書き方に置き換えてみてください。
それだけで、「速くて、読みやすくて、バグりにくい文字列組み立て」に、一段レベルアップできます。

タイトルとURLをコピーしました