かとじゅんの技術日誌

技術の話をするところ

文字列の結合をやるからといって、すぐにStringの+をつかってはいけない

文字列の結合っていろいろなやり方があるのですが、みなさんはどんなやり方をされていますか?

String firstName = "Junichi";
String lastName = "Kato";
String fullName = "";
fullName += firstName;
fullName += lastName;

とか、もちろんやってないですよね?
演算子ってわかって使わないとヒドイことになりますよw

性能をはかってみよう

ちょっと、簡単に以下のような性能をはかるテストを書いてみた。
文字列の連結方式としては、

  • Stringの+演算子
  • Stringのcontractメソッド
  • StringBufferのappendメソッド
  • StringBuilderのappendメソッド

が考えられるので、それらの性能をはかってみた。

package tests;

import org.junit.Test;

public class StringTest {

	@Test
	public void testPlusString() {
		String result = "";
		long start = System.currentTimeMillis();
		for (int i = 0; i < numItems(); i++) {
			result += lineForItem(i);
		}
		long finish = System.currentTimeMillis();
		System.out.println(String.format("String + time = %d", finish - start));
	}

	@Test
	public void testContractString() {
		String result = "";
		long start = System.currentTimeMillis();
		for (int i = 0; i < numItems(); i++) {
			result.concat(lineForItem(i));
		}
		long finish = System.currentTimeMillis();
		System.out.println(String.format("String#contract time = %d", finish - start));
	}

	
	@Test
	public void testStringBufferAppendString() {
		StringBuffer result = new StringBuffer();
		long start = System.currentTimeMillis();
		for (int i = 0; i < numItems(); i++) {
			result.append(lineForItem(i));
		}
		long finish = System.currentTimeMillis();
		System.out.println(String.format("StringBuffer#append time = %d",
				finish - start));
	}

	@Test
	public void testStringBuilderAppendString() {
		StringBuilder result = new StringBuilder();
		long start = System.currentTimeMillis();
		for (int i = 0; i < numItems(); i++) {
			result.append(lineForItem(i));
		}
		long finish = System.currentTimeMillis();
		System.out.println(String.format("StringBuilder#append time = %d",
				finish - start));
	}

	private String lineForItem(int i) {
		return String.valueOf(i);
	}

	private int numItems() {
		return 10000;
	}

}

ちなみに、上記の処理はEffictive Javaの項目51を参考にしている。
このテストは、10000回のループで次々に文字列を連結していくテスト。ループで数値の桁も繰り上がり連結する文字列も長くなっていく。

テスト結果

わたしの環境でのテスト結果はこちら。単位はmsec。

String + time = 593
String#contract time = 6
StringBuffer#append time = 3
StringBuilder#append time = 2

String#+ > String#contract > StringBuffer#append > StringBuilder#append
Stringの+はひどいことにw StringBuilder#appendの約297倍とか。
まだ、contractを使ったほうがマシ。
StringBufferと、StringBuilderは同期化のコストが若干ありそうですが、この程度なら気にするレベルではなさそうです。まぁ、同期化が必要ないところではStringBuilderが基本ということで。

Stringの+を使うと場合によっては、二つの文字列がコピーされてしまうようだ。。。Stringは不変オブジェクトだから仕方あるまいw

javacの+演算子の最適化

JDK1.5で「+演算子」が実際にどう変換されるか、jadを使って見てみた。

String a = "abc" + "def";

の場合はコンパイラの最適化で

String a = "abcdef";

に変換されるからよい。

String s = "def";
String str = "abc" + s;

は、

String s = "def";
String str = (new StringBuilder("abc")).append(s)
	.toString();

に最適化される。まぁ、これもよい。

String str = "abc";
str += "def";
str += "ghi";

は、

String str = "abc";
str = (new StringBuilder(String.valueOf(str)))
	.append("def").toString();
str = (new StringBuilder(String.valueOf(str)))
	.append("ghi").toString();

になってしまうらしいwww これ確実に死亡フラグですねw

Stringの+を使うところは、たとえば、一行のための処理とか、文字列が小さい場合の連結とかぐらいかな。それでも高頻度に実行される場合は避けるべき。このサイトでも、一行の処理ならよいが(というのはjavaコンパイラがStringBuilderのコードに最適化するから)、複数行やループ内ではStringBuilderを使わないと効率が悪いと言及されている。

まぁ、上記のような脳内変換をしたくない人は、極力、StringBuilderを使いましょう。
お仕事でプログラムする場合は絶対頭に入れておいてくださいwww