13
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

string・StringBuffer・StringBuilderの特徴&性能比較

Last updated at Posted at 2023-04-15

はじめに

Javaには文字列を扱うにはStringStringBufferStringBuilder3つの参照型クラスがあります。

この3つは「文字列を扱う」という共通点がありますが、それぞれの特徴があります。

この記事では、StringStringBufferStringBuilder3つの参照型クラスの差を調べ、どんな状況にどれを使えばいいのかについて勉強していきたいと思います。

StringBuffer・StringBuilderとは

StringBuffer・StringBuilderは文字列の演算(結合または変更)するとき、主に使われます。

もちろん、Stringも+演算concat()メソッドを使って文字列を結合することができます。

しかし、Stringの+演算で文字列を結合すると、結合された文字列の新たなStringインスタンスが生成されます。つまり、+演算をすればするほどメモリの無駄遣いや処理速度低下など性能が低下の原因になり得ます。

String str1 = "aaa";
str1 += " bbb";
str1 += " ccc";
System.out.println(str1); // aaa bbb ccc
// 操作は簡単だが、処理速度が遅い

だから、Javaはこのような文字列演算に特化したクラスを提供しています。StringBufferはクラス内部でバッファ(buffer)という空間があり、このbufferを使って文字列演算をすることで、性能を向上させています。

StringBuffer sb = new StringBuffer("aaa");
sb.append(" bbb");
sb.append(" ccc");
System.out.println(sb.toString()); // aaa bbb ccc
// Stringの+演算よりは複雑そうだが、処理速度は速い

後で述べますが、StringBufferとStringBuilderの大きな差はSynchronized機能の有無です。

StringBufferのbufferは基本的に16個の文字を保存できます。またbufferのサイズはコンストラクタで設定できます。

文字列演算で結合する文字列のサイズがbufferのサイズより大きいと自動的に文字列に併せてbufferのサイズが変更されます。

StringBufferのbufferサイズはcapacity()メソッドで確認できます。

StringBuffer sb = new StringBuffer(); // bufferの基本サイズは16
System.out.println(sb.capacity());

sb.append("aaa bbb ccc ddd eee");
System.out.println(sb.capacity()); // 演算後bufferの基本サイズは34

Stringbufferの内部メソッド等の詳細はクラスBuffer_Docsで確認できます。

Stringと(StringBuffer、StringBuilder)を比較

文字列型の不変と可変

Stringは不変

JavaでStringオブジェクトのデータは変更できません

下記のコードを見ると"hello"のデータを持っているstr変数に" world"を結合します。
結合によって"hello world"というデータが作られ、新しいメモリ領域に保存されます。"hello"が保存されている領域は参照する変数がなくなるため、ガベージコレクションによって解放対象になります。

String str = "hello";
str = str + " world";

System.out.println(str); // hello world

image.png
このようにStringは不変オブジェクトのため、データが変わらない文字列を頻繁に読み込む際に使います。

しかし、文字列の演算が頻繁に行われる場合Stringクラスを使うとメモリに開放対象の領域が多くなり、アプリケーションのパフォーマンスに悪影響を及ぼします。

Stringオブジェクトの詳細は私の記事【Java】Stringについてを参考してください。

Stringbuffer・StringBuilderは可変

Stringbuffer・StringBuilderは文字列を扱うところはStringオブジェクトと同じですが、文字列演算で既存のオブジェクトサイズを超えると既存のbufferのサイズを増やせるので、可変的だという差があります。

そのため、Stringbuffer・StringBuilderはappend()delete()等のAPIを用いて同一オブジェクト内で文字列を変更することができます。したがって文字列の結合、変更、削除が頻繁に行われる場合使うとパフォーマンス向上ができます。

StringBuffer sb= new StringBuffer("hello");
sb.append(" world");

img1.daumcdn.png

文字列結合の性能比較

Javaで文字列を結合する方法は4つあります。

+演算String.concat()メソッド、StringBufferStringBuilderでそれぞれの特徴について説明します。

文字列結合(+演算)

Javaでプログラミングをするとき、文字列を扱う場合が多いです。例えば文字列を結合するときは"hello"+" world"のように+演算で結合しがちです。

そこで、Javaの+演算はどのように行われているのか調べてみました。

実際にJavaは文字列を+演算で結合すると、コンパイル前に内部でStringBuilderクラスを宣言して、その文字列を返します。

つまり、"hello"+" world"の文字列結合があれば、これはnew StringBuilder("hello").append(" world").toString()と同じわけです。

String str1 = "hello" + " world";
String str2 = new StringBuilder("hello").append(" world").toString()
//2つのコードは同じです。

このように文字列演算が少ない場合、大きな差はないとみられます。

しかし、次のように文字列演算が多い場合、単純に+演算で結合するとメモリと処理の性能が低下します。

String str1 = "a";

for(int i = 0; i < 10000; i++) {
    str1 = str1 + "a";
}
// 上のコードは下のコードと同じです。
// つまり、毎回 new StringBuilder() オブジェクトメモリを生成し、また変数に代入することを一万回繰り返します。

String str2 = new String("a");

for (int i = 0; i < 10000; i++) {
	str2 = new StringBuilder(str2).append("a").toString();
}

上記のように文字列演算が多いときは、最初からStringBuilderで文字列を宣言した方がよさそうです。

StringBuilder sb = "a";

for(int i = 0; i < 10000; i++) {
    sb = new StringBuilder(sb).append("a").toString();
}

String.concat()

次のコードがString.concat()メソッドです。

String str = "hello";
str = str.concat(" world");

concat()メソッドは連結する文字列をnew String()でインスタンスを生成して結合します。したがって文字列をconcat()メソッドで連続に結合すれば、アプリケーションの性能低下になり得ます。

文字列の結合性能比較結果

+演算String.concat()メソッド、StringBufferStringBuilder4つの方法で文字列結合による性能は下記の図で見られます。
img1.daumcdn.png

この結果によって、「Javaで文字列を扱うときは、必ずStringBufferとStringBuilderを使うべきだ。」と考えるはずですが、それは文字列の使い方によると思います。

なぜかというと、StringBufferやStringBuilderを宣言するときはbufferのサイズを設定する必要があります。これはかなりのリソースがかかる作業です。加えて文字列の変更時にbufferのサイズの変更やデータの修正など色んな演算が必要なので、演算の回数が少ない場合はStringクラスを使うのが性能的に良いかと思います。

また、Stringクラスはサイズが決まっているので、単純にデータを読み込むだけなら処理速度は速いです。

つまり、文字列の変更が少ない場合はString文字列の結合や変更などの作業が多い場合はStringBufferを使うのがいいと思います。

StringBufferとStringBuilderの違い

マルチスレッド環境での安全性(Thread Safe)

StringBufferとStringBuilderはどっちもbufferを持っている可変オブジェクトという特徴があり、提供しているメソッドも同じです。

で、この2つの違いは何でしょうか。
それはマルチスレッド(Thread)環境で安全(Safe)かどうかそれだけです。
image.png

実際にコードでテストしてみます。

次のコードはStringBufferとStringBuilderを宣言して、2つのマルチスレッドでStringBufferとStringBuilderのオブジェクトにそれぞれ"A"を1万回追加します。

2つのスレッドが"A"を1万回ずづ追加するので文字列のサイズは20000になるはずです。

StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();

new Thread(() -> {
	for(int i=0; i<10000; i++) {
     	stringBuffer.append("A");
		stringBuilder.append("A");
    }
}).start();

new Thread(() -> {
	for(int i=0; i<10000; i++) {
     	stringBuffer.append("A");
		stringBuilder.append("A");
    }
}).start();
結果
StringBuffer.length : 20000
StringBuilder.length : 19888

しかし、上の結果ではサイズが違うのが分かります。

このようにStringBuilderはマルチスレッド環境でスレッドが同時に実行された場合、片方のスレッドで読み書きしているデータをもう一方のスレッドが読み書きしてしまう恐れがあります。

一方、StringBufferは同期化処理(Synchronized)によるThread Safeでサイズが20000で正しく出力されました。

処理速度比較

では、今まで演算方法について調べてみましたが、これらの中でどれが処理速度が速いのか確認します。

for文"A"を100万回loopして追加するロジックでテストしてみます。

String str = ""; 
long plusStartTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
	str = str + "a";
}
long plusEndTime = System.currentTimeMillis();

StringBuilder stringBuilder = new StringBuilder();
long builderStartTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
	stringBuilder = stringBuilder.append("a");
}
long builderEndTime = System.currentTimeMillis();


StringBuffer stringBuffer = new StringBuffer();
long bufferStartTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
	stringBuffer = stringBuffer.append("a");
}
long bufferEndTime = System.currentTimeMillis();
結果
「+演算 」: 78086(ms)
「StringBuffer」: 11(ms)
「StringBuilder」: 10(ms)

文字列の演算にかかった時間を見ると性能的にはThread Safeを採用しないStringBuilderの方が一番早いです。

image.png
このグラフをでも分かる通り処理にかかった時間はString>StringBuffer>StringBuilder順でした。

もしシングルスレッド環境ではStringBuilderを使うのが性能面で一番優れていますが、
実際、今日はマルチスレッド環境で動いてるサービスが多いだけ、安全なStringBufferでコードを書くのがいいと思います。(StringBufferとStringBuilderの処理速度の差はほぼなし)

まとめ

String StringBuffer StringBuilder
不変、可変 不変 可変 可変
Thread Safe O O X
処理速度 遅い 早い 早い
使いどころ 文字列演算が少ない、
マルチスレッド環境
文字列演算が多い、
マルチスレッド環境
文字列演算が多い、
シングルスレッド環境

表で分かる通り読み込む作業が多い場合はStringそのほかはStringBufferを使うようにします。

以上です。

次は、JavaのEnumについて記事を書こうと思います。

最後まで読んでいただき、ありがとうございました。

参考サイト

javaSilver 黒本
http://www.tcpschool.com/java/java_api_stringBuffer
https://ifuwanna.tistory.com/221
https://javacan.tistory.com/entry/41
https://gogomalibu.tistory.com/96
https://venishjoe.net/post/java-string-concatenation-and-performance/
https://madplay.github.io/post/difference-between-string-stringbuilder-and-stringbuffer-in-java
https://kotlinworld.com/36

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?