Java

Java8での文字列連結

More than 3 years have passed since last update.


これは何?

Stream使ってzipできないかなーと思って試行錯誤していたらちょっと気になったので書いた。

実際はJava8時代の文字列連結まとめ - きしだのはてなに便乗してたりして。

連結対象の文字列は配列として保持。alphaがそれ。実行時間は平均取るのが面倒臭かったので500万回のループを実行したときの結果。つまりあまり参考にならないので注意。

static final String[] alpha = new String[]{

"0", "1", "2", "3", "4",
"5", "6", "7", "8", "9",
"a", "b", "c", "d", "e",
"f", "g", "h", "i", "j",
"k", "l", "m", "n", "o",
"p", "q", "r", "s", "t",
"u", "v", "w", "x", "y",
"z"};


注記

なにか理由がない限りArrays.stream(T[])の代わりにStream.of(T...)を使ったほうがいいかもしれません。プリミティブな配列(int[]とか)じゃなければTの配列はT...形式の引数をとるメソッドでは同一視されるため。

ビュー数やストック数が増えているので一応追記。(2014.11.2)


1. よくある文字列連結

StringBuilder buf = new StringBuilder();

for(String str : alpha){
buf.append(str);
}
buf.toString();

なんだかんだいってやっぱり速い。4552ms。


2. Stream APIで書く

String str = Arrays.stream(alpha)

.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();

4620ms。まあ誤差の範囲。複数実行させたところ1を超すことだって何度かあった。よくわからんけどJIT最適化うんぬんかな?


3. もっと本格的に。

String str = Arrays.stream(alpha).collect(Collectors.joining());

引用元忘れたけどどっかから持ってきたコード。5267ms。やっぱり標準APIでスッキリかけるのはコードの見通しが良くなっていい。


4. そもそも文字列joinだけならjava8からメソッド追加されてる

String str = String.join("", alpha);

単純なのはこれでいいかもしれないけどStream APIとか取り出してる時点でこれは反則?6490ms。


5. StringBuilder使いなさい!と頭ごなしに叱られそうなコード

String buf = "";

for(String str : alpha){
buf += str;
}

まあ試行回数が少ないからね。7667ms。


6.魔窟。

String str = String.join("", 

Arrays.stream(alpha)
.collect(Collectors.toList()) );

あとでコード見なおしてみれば、まあ無駄な操作が多すぎる。Stream API使って最初に書いたコード。可読性にも乏しいし、わざわざList作ってからまたjoinかけてるところなんてまさに無意味。

実行したらあまりに遅くて、自分がわけわからんコーディングしてるのにStream APIのせいにして小一時間現実逃避してました。12114ms。


7. 関数型だぞー。すごいぞー。かっこいいぞー。

String str = Arrays.stream(alpha).reduce("", String::concat);

俺は関数型の人間なんだ!fold(reduce)使いこなしてやったぜ!!どうだ参ったか!

関数型プログラミングのセミナー帰りのノリと勢いで書きそうなコード。reduceの代わりにcollectを使えば速いのに気がついてないご様子。

注釈 reduceは終端操作だが、Stringを返すようにしなければならず、collectと異なりStringBuilderが使えない

ここまでで一番実行速度が遅いコードで、13015ms。(13秒!?)

P.S. 関数型プログラミング畑のかた、侮辱しているわけではありません。しかしながら十中八九気分を害すると思うのでここでお詫びさせていただきます。申し訳ありません。


コード

import java.lang.System;

import java.lang.String;
import java.lang.StringBuilder;
import java.util.Arrays;
import java.util.stream.Stream;
import java.util.stream.Collectors;

public class Test{
static final String[] alpha = new String[]{
"0", "1", "2", "3", "4",
"5", "6", "7", "8", "9",
"a", "b", "c", "d", "e",
"f", "g", "h", "i", "j",
"k", "l", "m", "n", "o",
"p", "q", "r", "s", "t",
"u", "v", "w", "x", "y",
"z"};
static long loop = 5000000;

public static void main(String[] args){
long span;
/* 1: よくある文字列連結 ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
StringBuilder buf = new StringBuilder();
for(String str : alpha){
buf.append(str);
}
buf.toString();
}
span = System.currentTimeMillis()-span;
System.out.println(span); //4552

/* 2: 1のStream API版? ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
String str = Arrays.stream(alpha)
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
}
span = System.currentTimeMillis()-span;
System.out.println(span); //4620

/* 3: collect使ってみる ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
String str = Arrays.stream(alpha).collect(Collectors.joining());
}
span = System.currentTimeMillis()-span;
System.out.println(span); //5267

/* 4: そもそも文字列joinだけならjava8からメソッド追加されてる ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
String str = String.join("", alpha);
}
span = System.currentTimeMillis()-span;
System.out.println(span); //6490

/* 5: StringBuilder使いなさい!と頭ごなしに叱られそうなコード ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
String buf = "";
for(String str : alpha){
buf += str;
}
}
span = System.currentTimeMillis()-span;
System.out.println(span); //7667

/* 6: 最初に書いた残念コード ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
String str = String.join("",
Arrays.stream(alpha)
.collect(Collectors.toList()) );
}
span = System.currentTimeMillis()-span;
System.out.println(span); //12114

/* 7: 高階関数学んだからという理由で、6の次に書いたクソコード ****/
span = System.currentTimeMillis();
for(long i=0; i<loop; i++){
String str = Arrays.stream(alpha).reduce("", String::concat);
}
span = System.currentTimeMillis()-span;
System.out.println(span); //13015
}
}

実行結果

user@ubuntu:~$ java8 Test

4552
4620
5267
6490
7667
12114
13015