PR

JavaのStringBuilderクラスの使い方/動作原理を3分でわかりやすく

Java

このページでは、JavaのStringBuilderについて「そもそも何者?」「なぜ使うのか」「どう使うのか」という基本から、内部的な動きまでをわかりやすく解説していきます。文字列操作のパフォーマンス向上を目指す際には欠かせないクラスですので、しっかりと理解しておきましょう。

スポンサーリンク

StringBuilderとは

JavaのString参考 「String」のいろは)は不変(immutable)と呼ばれ、生成後はその内容を変更できません。例えば以下のようなコードで文字列を結合すると、内部的には新しいStringオブジェクトが毎回生成されます。

String str = "Hello";
str = str + " World"; 

"Hello"に対して" World"を付け足したとき、"Hello World"という別のStringオブジェクトが新たに作られます。
このような結合処理をループなどで頻繁に行うと、不要なオブジェクトの生成が多発し、パフォーマンスが低下してしまう可能性があるという問題点があります。

StringBuilderの役割

StringBuilderは、上記の問題点を解決するためのクラスです。

  • 可変(mutable)な文字列バッファを内部に持ち、そこに文字を追加・編集していくイメージ。
  • 文字を追加するときも、新しく文字列オブジェクトを作るのではなく、内部バッファに書き足していきます。
  • 頻繁に文字列を操作する処理でパフォーマンスを大きく改善します。

内部的な動作原理

可変バッファ(配列)を使った設計

StringBuilderは、内部に文字列を保持するための配列(文字配列)を用意しています。例えば初期容量capacityが16だとすると、最初は16文字分のバッファが確保されているイメージです。

StringBuilder sb = new StringBuilder(); // capacityがデフォルト16

ここに文字をappend(追記)すると、まだバッファに余裕がある限り、同じ配列の空き領域を使って文字を追加していきます。

バッファが足りなくなったら自動拡張

たとえば現在のバッファが16文字分確保されていて、そこに17文字目を追加しようとするとどうなるでしょうか?
その場合は、内部的により大きな配列を新たに確保し、古い内容をすべてコピーしてバッファを拡張します。拡張頻度を減らすために、単純な+1ではなく、容量を2倍程度に増やす実装となっているため、動的に成長していきます。

StringBuilderとStringBufferの違い

JavaにはStringBufferというクラスも存在し、こちらも可変文字列操作のためのクラスです。

  • StringBufferスレッドセーフ(thread-safe)で、マルチスレッド環境で安全に利用できるように同期化が行われています。
  • StringBuilderは同期化が行われていない分、シングルスレッド環境でのパフォーマンスが高いです。

基本的にマルチスレッド環境で共有して利用する場合以外は、より高速なStringBuilderを使うのが一般的と考えてよいでしょう。

よく使われるメソッド

appendメソッド

文字列や数値、オブジェクトなどを連結していくときに最もよく使われるメソッドです。

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
sb.append(123);
String result = sb.toString(); // "Hello World123"

insertメソッド

特定の位置に文字列や文字、数値などを挿入します。

StringBuilder sb = new StringBuilder("Hello World");
sb.insert(5, ","); 
// "Hello, World"

deleteメソッド / deleteCharAtメソッド

指定した範囲(または指定した1文字)を削除します。

StringBuilder sb = new StringBuilder("Hello, World");
sb.delete(5, 6); 
// "Hello World"

replaceメソッド

指定した範囲を別の文字列に置き換えます。

StringBuilder sb = new StringBuilder("Hello World");
sb.replace(5, 11, ", Everyone!");
// "Hello, Everyone!"

reverseメソッド

文字列を逆順にします。

StringBuilder sb = new StringBuilder("abc");
sb.reverse(); 
// "cba"

capacity / ensureCapacityメソッド

現在のバッファ容量を確認したり、特定の容量を事前に確保したりできます。

StringBuilder sb = new StringBuilder();
System.out.println(sb.capacity()); // デフォルト16
sb.ensureCapacity(100);           // 必要に応じてキャパシティを100以上に確保

使用例: ループでの文字列結合

ループ内で文字列をどんどん追加していく場合、StringBuilderを使わないとオブジェクト生成が頻繁に起きてしまいます。以下の例は悪い例と良い例です。

悪い例 Stringのみで結合

String text = "";
for (int i = 0; i < 100000; i++) {
    text = text + i; // 毎回新たなStringオブジェクトを生成
}

良い例 StringBuilderで一気に結合

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append(i);
}
String text = sb.toString();

後者ではオブジェクトの再生成が少なく、処理速度が大幅に向上します。

まとめ StirngBuilder

  1. JavaのStringは不変(immutable) のため、文字列操作を頻繁に行うとパフォーマンスが低下しやすい。
  2. StringBuilderは可変バッファを内部に持ち、文字列を効率良く扱える。
  3. 内部のバッファ容量が足りなくなると、自動的に拡張される仕組み。
  4. シングルスレッド環境であれば、StringBuilderが基本的な選択肢(マルチスレッドならStringBuffer)。
  5. appendinsertなど多彩なメソッドを使い、効率良く文字列を操作できる。
  6. ループなどで文字列連結を多用する場合、StringBuilderを使うとパフォーマンスが大幅向上する。

基本編はこちらで終了。ここからは補足的により本質を理解するための説明です。

StringBuilderの本質は「可変長配列(char[]の動的利用)」

ポイント 内部的にはchar[](またはバイト配列)が使われている

StringBuilderは、Javaの内部実装として可変長配列(動的配列)参考 配列とは?)を使っています。初期化時に一定サイズ(たとえばデフォルト16)の配列を持ち、そこに文字を格納していきます。Java 9以降の実装ではbyte[]を使った「compact strings」という最適化が導入されましたが、本質的には「内部で連続したバッファを用いて文字を蓄えている」という点は変わりません。

ポイント  “可変”の仕組み: リサイズ(拡張)のタイミング

バッファに収まりきらなくなると、より大きな配列を新たに確保し、既存の内容をコピーしてからバッファ参照を切り替える、というフローで拡張します。
このときの拡張は1文字ずつではなく、大きめに倍増(※Java標準の実装では (oldCapacity + 1) * 2 など)されるため、頻繁に拡張処理が走ることを避けられます。これを「アモータイズされたコスト」と呼び、1回ごとのappendは平均すると非常に高速になります。

ポイント 文字列の末端を示す「current position」

StringBuilderには、配列全体の容量(capacity)とは別に、「いま末端がどこか」を示すインデックスが保持されています。

  • append()で文字を追記する際は、この末端インデックス位置に文字列を書き込み、末端インデックスを更新
  • バッファ容量を超える場合だけ、上記の拡張処理を行う。

こうすることで、何度も文字を追加しても同じバッファ領域上に書き込むだけで済み、Stringのようなオブジェクト新規生成は行われません。

実装に基づく多彩な操作が可能になる理由

insert, delete, replace, reverseなど

  • insert: 途中に挿入する場合は、内部で要素をシフトし、挿入位置に文字列を書き込みます。
  • delete, replace: 指定範囲の要素を削除したり、新たに上書きしたりするだけ。
  • reverse: バッファを単純に頭から末端まで走査し、要素を入れ替えるだけ。

このように配列上で要素の移動・書き換えを行うことで、オブジェクトを新たに作らなくても文字列操作が実行可能となります。

toString()で最終的にStringを生成

toString()を呼び出すと、内部の配列内容から新たにStringオブジェクトを作るステップが入ります。
これにより、「最終的に不変(immutable)な文字列として扱いたい」ときはStringに変換し、それまではStringBuilderで可変操作を行う、という柔軟な使い分けが可能です。

どんなことが実現できるのか? ~本質理解から見るメリット~

頻繁な連結処理の高速化

最も代表的なのは、「繰り返し文字列連結を行う」ような場面でのパフォーマンス向上です。

  • 例: ログメッセージ生成、HTML/JSON/XMLなどテキスト構造の生成、レポートの組み立て
  • 新しいStringを作らずに内部バッファを伸長していくため、大量のオブジェクト生成を回避できます。

途中挿入や削除にも対応

Stringは不変なので、途中で文字列を差し込む・削除する場合、通常は新しいStringを生成するコストがかかります。
StringBuilderなら配列上で書き換えできるため、可変長配列の操作に近い感覚で扱えます。

可変でありながら最終的には不変文字列に変換できる

StringBuilder内部のバッファはあくまで一時的な編集用という位置づけです。
最終成果物が完成したらtoString()でオブジェクト化し、以降は不変のStringとして扱う。この流れで、安全性と効率性の両立が実現しています。

まとめ: “動的配列+不変文字列の住み分け”という設計

Javaの文字列において

  1. String(immutable) … 文字列を不変オブジェクトとして扱う(安全性、スレッドセーフ、ハッシュ計算の保持などの利点)
  2. StringBuilder(mutable) … 可変長配列を利用し、動的に文字列を構築・編集する(パフォーマンス優位)

という2種類を用意しているのは、用途に応じた最適解を選べるようにするためです。

  • 変更の必要がなく、不変であることで安全や利便が得られる場所ではString
  • 頻繁に変更・連結する必要があればStringBuilder
    こうした住み分けを理解できると、Java言語の文字列設計の意図もより深く把握できます。

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