StringBuilder / StringBuffer — 文字列連結の効率化
文字列を何度も結合・加工する処理では、Stringの「不変性」が足かせになりがちです。StringBuilder と StringBuffer は“可変のバッファ”で連結や挿入・置換を高速にします。初心者向けに違い、使いどころ、基本操作をかみ砕いて解説します。
違いと使い分け
- 特性の違い:
- StringBuilder: 非同期(スレッドセーフでない)だが高速。単一スレッドでの連結に最適。
- StringBuffer: ほぼ同等のAPIだが同期化されている(メソッドがsynchronized)。マルチスレッドで同じインスタンスを共有して操作する場合に安全。
- 使い分けの指針:
- 単一スレッドの処理: StringBuilder 一択。
- 共有インスタンスへ複数スレッドが書き込む: StringBuffer。ただし、実務では「各スレッドが自分用のStringBuilderを持つ」設計が一般的。
- Stringとの違い:
- String: 不変。結合のたびに新しいインスタンスを生成しがちで非効率。
- Builder/Buffer: 内部バッファに追記・変更し、最後に toString() で不変のStringに変換。
基本操作とよく使うメソッド
- append(追記):
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append(42);
String out = sb.toString(); // "Hello 42"
Java- insert(挿入):
StringBuilder sb = new StringBuilder("ABCDEF");
sb.insert(3, "-"); // "ABC-DEF"
Java- delete / deleteCharAt(削除):
StringBuilder sb = new StringBuilder("2025/12/04");
sb.delete(4, 5); // "202512/04"([開始含む, 終了含まない])
sb.deleteCharAt(6); // 一文字だけ削除
Java- replace(置換):
StringBuilder sb = new StringBuilder("price=1000");
sb.replace(6, 10, "980"); // "price=980"
Java- reverse(反転):
StringBuilder sb = new StringBuilder("abcdef");
sb.reverse(); // "fedcba"
Java- capacity / ensureCapacity(容量管理):
StringBuilder sb = new StringBuilder(1024); // 初期容量指定で再拡張を減らす
sb.ensureCapacity(5000); // 先に十分な容量を確保
Java- toString(最終生成):
String result = sb.toString(); // 不変のStringに変換
Java実践パターン(表示・ログ・CSV 連結)
- 区切り付きの連結(先頭だけ区切りなし):
String[] items = {"A","B","C"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.length; i++) {
if (i > 0) sb.append(",");
sb.append(items[i]);
}
String csv = sb.toString(); // "A,B,C"
Java- ログメッセージの構築(キー情報を明確に):
String userId = "u-001";
String action = "login";
StringBuilder log = new StringBuilder(128);
log.append("user=").append(userId)
.append(" action=").append(action)
.append(" ts=").append(System.currentTimeMillis());
System.out.println(log.toString());
Java- テンプレート+繰り返し(大量結合の高速化):
StringBuilder sb = new StringBuilder(2048);
for (int i = 0; i < 1000; i++) {
sb.append("line ").append(i).append('\n');
}
String bulk = sb.toString();
Java- 部分編集(中間を置換・削除):
StringBuilder sb = new StringBuilder("INV-1001: pending");
int colon = sb.indexOf(":"); // StringBuilderにもindexOfあり
sb.replace(colon + 2, sb.length(), "confirmed");
// "INV-1001: confirmed"
Java例題で身につける(解説つき)
- 例題1: 連結 vs Stringの+ を体感
- ポイント: 不変のStringは都度新規生成。繰り返しはStringBuilderで一括追記。
// ループで1000回結合(+ は非推奨)
String s = "";
for (int i = 0; i < 1000; i++) s += i; // 遅くなりがち
// StringBuilder で同じ処理(推奨)
StringBuilder sb = new StringBuilder(4000);
for (int i = 0; i < 1000; i++) sb.append(i);
String t = sb.toString();
Java- 例題2: CSVの安全生成(エスケープ込み)
- ポイント: 繰り返し+エスケープでも可読性を保てる。
String[] fields = {"a,b", "x\"y", "z"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fields.length; i++) {
if (i > 0) sb.append(',');
String f = fields[i].replace("\"", "\"\"");
sb.append('"').append(f).append('"');
}
String csv = sb.toString(); // "a,b","x""y","z"
Java- 例題3: 途中挿入で整形
- ポイント: insertで位置指定し、既存文字列の中に差し込める。
StringBuilder sb = new StringBuilder("20251204");
sb.insert(4, '/').insert(7, '/'); // "2025/12/04"
Javaすぐ使えるテンプレートと注意点
- 基本連結テンプレ:
StringBuilder sb = new StringBuilder(initialCapacity); // 省略可
sb.append(a).append(b).append(c);
String out = sb.toString();
Java- 区切り連結テンプレ(先頭だけ除外):
for (int i = 0; i < values.size(); i++) {
if (i > 0) sb.append(delimiter);
sb.append(values.get(i));
}
Java- 容量最適化テンプレ(予測サイズが分かる場合):
int estimated = rows * avgLineLength;
StringBuilder sb = new StringBuilder(Math.max(estimated, 64));
Java- 注意点:
- ラストのtoStringを忘れない: Builder/BufferはそのままではStringではない。
- スレッド共有は避ける: 共有するならStringBuffer、基本はスレッドごとにBuilderを持つ。
- indexの範囲に注意: insert/replace/deleteは「開始含む・終了含まない」。範囲外は例外。
- 使い終わりの再利用は慎重に: 同じインスタンスを再利用すると前の内容が残る。
setLength(0)でクリア可能。sb.setLength(0); // 内容クリア(容量は保持)
まとめ
- 大量・繰り返しの連結は StringBuilder、マルチスレッド共有が必要なら StringBuffer。
- append/insert/delete/replace を押さえれば、表示・ログ・CSV・テンプレート作成が快適。
- capacityの先読みで再拡張を減らし、パフォーマンスが安定。
