StringBuilder vs文字列の連結にtoString()Java
-
20-09-2019 - |
質問
の2 toString()
実装では、以下が優先:
public String toString(){
return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}
または
public String toString(){
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:").append(b)
.append(", c:").append(c)
.append("}")
.toString();
}
?
これまでの3物件ではなく、なんとポイントだから +
concatる StringBuilder
?
解決
バージョン1が好ましいので短縮と のコンパイラがでバージョン2 な性能差があります。
これまでの3 物件のない場合があったので、それを作 違いがなんとポイントだ スイッチからconcatるビルダー?
のん列のループが通常のコンパイラの代替できない StringBuilder
によりそのものです。
他のヒント
キーを使用して、1つの場所ですべての単一連結を書いたり時間をかけてそれを蓄積しているかどうかです。
あなたが与え例えば、明示的にStringBuilderを使用しても意味がありません。 (あなたの最初のケースのためにコンパイルされたコードを見てください。)
しかし、あなたは、例えば、文字列を構築している場合ループ内で、StringBuilderのを使用します。
hugeArrayは、文字列の何千も、このようなコードが含まれていることを想定し、明確にするために、
...
String result = "";
for (String s : hugeArray) {
result = result + s;
}
と比較して非常に時間およびメモリ無駄である
...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
sb.append(s);
}
String result = sb.toString();
う:
String.format( "{a: %s, b: %s, c: %s}", a, b, c );
...で短くすことはないですね。
思 ない 最適化はこの速度を使用しない限り内部にループは、高い繰り返し回数 や い性能を測定します。
同意する場合は出力のパラメータではなく混乱などのコメントとします).この場合においスイッチにより可読な形式にも使用 ToStringBuilder apache-コモンズ"ケられ"は発生しませんからの回答のマットb)、無視する。
ほとんどの場合は、次の2つのアプローチの間の実際の差は見られませんが、それはこのような最悪のシナリオを構築するのは簡単です。
public class Main
{
public static void main(String[] args)
{
long now = System.currentTimeMillis();
slow();
System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");
now = System.currentTimeMillis();
fast();
System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
}
private static void fast()
{
StringBuilder s = new StringBuilder();
for(int i=0;i<100000;i++)
s.append("*");
}
private static void slow()
{
String s = "";
for(int i=0;i<100000;i++)
s+="*";
}
}
出力されます:
slow elapsed 11741 ms
fast elapsed 7 ms
問題は、+ =は、文字列に追加するには、新しい文字列を再構築することであるので、それはあなたの文字列の長さ(両方の合計)。
に何かリニアコストだから - あなたの質問に:
第2のアプローチは、より高速になりますが、それは読みにくく、メンテナンスが難しくなります。 私が言ったように、あなたの特定のケースであなたはおそらく違いは表示されません。
私はまた、彼らは追加を使用している追記または+の.asを使用するかどうかという事実に私の上司と衝突していた(彼らは新しいオブジェクトが作成されるたびに言うように私はまだ把握カント)。 だから私はいくつかのR&D.Although私はマイケルBorgwardtの、について説明を愛するが、ちょうど誰かが本当に将来的に知っている必要があります場合は説明を見せたかっを行うことを考えています。
/**
*
* @author Perilbrain
*/
public class Appc {
public Appc() {
String x = "no name";
x += "I have Added a name" + "We May need few more names" + Appc.this;
x.concat(x);
// x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
//System.out.println(x);
}
public void Sb() {
StringBuilder sbb = new StringBuilder("no name");
sbb.append("I have Added a name");
sbb.append("We May need few more names");
sbb.append(Appc.this);
sbb.append(sbb.toString());
// System.out.println(sbb.toString());
}
}
と上記のクラスの解体は、
のように出てきます .method public <init>()V //public Appc()
.limit stack 2
.limit locals 2
met001_begin: ; DATA XREF: met001_slot000i
.line 12
aload_0 ; met001_slot000
invokespecial java/lang/Object.<init>()V
.line 13
ldc "no name"
astore_1 ; met001_slot001
.line 14
met001_7: ; DATA XREF: met001_slot001i
new java/lang/StringBuilder //1st object of SB
dup
invokespecial java/lang/StringBuilder.<init>()V
aload_1 ; met001_slot001
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
ldc "I have Added a nameWe May need few more names"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
aload_0 ; met001_slot000
invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
astore_1 ; met001_slot001
.line 15
aload_1 ; met001_slot001
aload_1 ; met001_slot001
invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
pop
.line 18
return //no more SB created
met001_end: ; DATA XREF: met001_slot000i ...
; ===========================================================================
;met001_slot000 ; DATA XREF: <init>r ...
.var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001 ; DATA XREF: <init>+6w ...
.var 1 is x Ljava/lang/String; from met001_7 to met001_end
.end method
;44-1=44
; ---------------------------------------------------------------------------
; Segment type: Pure code
.method public Sb()V //public void Sb
.limit stack 3
.limit locals 2
met002_begin: ; DATA XREF: met002_slot000i
.line 21
new java/lang/StringBuilder
dup
ldc "no name"
invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
astore_1 ; met002_slot001
.line 22
met002_10: ; DATA XREF: met002_slot001i
aload_1 ; met002_slot001
ldc "I have Added a name"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 23
aload_1 ; met002_slot001
ldc "We May need few more names"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 24
aload_1 ; met002_slot001
aload_0 ; met002_slot000
invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
pop
.line 25
aload_1 ; met002_slot001
aload_1 ; met002_slot001
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 28
return
met002_end: ; DATA XREF: met002_slot000i ...
;met002_slot000 ; DATA XREF: Sb+25r
.var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001 ; DATA XREF: Sb+9w ...
.var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
.end method
;96-49=48
; ---------------------------------------------------------------------------
上記の2つのコードからは、マイケルが唯一のSBオブジェクトが作成されright.Inそれぞれのケースで見ることができます。
以来、Java1.5単一線の連結には"+"、StringBuilder.append()を同じbytecode.
そのためのコードを読みやすさは、"+".
2つの例外:
- マルチスレッド環境:StringBuffer
- 連結にループ:StringBuilder/StringBuffer
最新のバージョンのJava(1.8)の解体(javap -c
の最適化の導入によるコンパイラです。 +
として sb.append()
を生成する非常に類似す。しかし、そういうときに価値のある検査を行い +
るためのループを実行します。
追加の文字列を+、forループ
Java:
public String myCatPlus(String[] vals) {
String result = "";
for (String val : vals) {
result = result + val;
}
return result;
}
ByteCode:(for
ループを抜粋)
12: iload 5
14: iload 4
16: if_icmpge 51
19: aload_3
20: iload 5
22: aaload
23: astore 6
25: new #3 // class java/lang/StringBuilder
28: dup
29: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload 6
38: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc 5, 1
48: goto 12
追加の文字列を使用stringbuilder.append
Java:
public String myCatSb(String[] vals) {
StringBuilder sb = new StringBuilder();
for(String val : vals) {
sb.append(val);
}
return sb.toString();
}
ByteCdoe:(for
ループを抜粋)
17: iload 5
19: iload 4
21: if_icmpge 43
24: aload_3
25: iload 5
27: aaload
28: astore 6
30: aload_2
31: aload 6
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc 5, 1
40: goto 17
43: aload_2
あのビット 無視差 ものです。第一の場合には、 +
使用した、新 StringBuilder
が作成されたループの繰り返し生成された結果が格納されることによって toString()
コ(29 41).していただく必要があり生成中間体の文字列が本当に必要のない使用 +
オペレーター for
ループを実行します。
invokedynamic
に変換されるので、Javaの9では、バージョン1は、より高速にする必要があります。
:詳細は JEP-280 の中に見つけることができますアイデアは、連結の必要性に値を受け入れるjava.lang.invoke.StringConcatFactoryへの単純なinvokedynamicの呼び出し、と全体のStringBuilderアペンドダンスを交換することです。
はApache Commonsの-ラングはを持っています使用する超簡単ですToStringBuilder のクラス。これは、追記ロジックだけでなく、あなたがあなたのtoStringを見てみたいかのフォーマットを扱う両方の素晴らしい仕事をしていません。
public void toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("a", a);
tsb.append("b", b)
return tsb.toString();
}
com.blah.YourClass@abc1321f[a=whatever, b=foo]
のような出力が返されます。
以上の凝縮形で、連鎖を使用します:
public void toString() {
return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}
それとも、クラスのすべてのフィールドを含めるためにリフレクションを使用する場合:
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
あなたがしたい場合は、また、ToStringメソッドのスタイルをカスタマイズすることができます。
パフォーマンス上の理由のために利用 +=
(String
連結)することは勧められない。ありがとうございました:Java String
は不変なので、毎回新たに連結が行われ新た String
作成した、新しいものが異なる指紋からの既存の 文字列のプール ).新しい文字列が圧力にGCびが鈍化し、プログラム:オブジェクトを作成する。
以下のコードで実践的に明確にします。
public static void main(String[] args)
{
// warming up
for(int i = 0; i < 100; i++)
RandomStringUtils.randomAlphanumeric(1024);
final StringBuilder appender = new StringBuilder();
for(int i = 0; i < 100; i++)
appender.append(RandomStringUtils.randomAlphanumeric(i));
// testing
for(int i = 1; i <= 10000; i*=10)
test(i);
}
public static void test(final int howMany)
{
List<String> samples = new ArrayList<>(howMany);
for(int i = 0; i < howMany; i++)
samples.add(RandomStringUtils.randomAlphabetic(128));
final StringBuilder builder = new StringBuilder();
long start = System.nanoTime();
for(String sample: samples)
builder.append(sample);
builder.toString();
long elapsed = System.nanoTime() - start;
System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);
String accumulator = "";
start = System.nanoTime();
for(String sample: samples)
accumulator += sample;
elapsed = System.nanoTime() - start;
System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);
start = System.nanoTime();
String newOne = null;
for(String sample: samples)
newOne = new String(sample);
elapsed = System.nanoTime() - start;
System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}
結果は報告されます。
builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us
builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us
builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us
builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us
builder - 10000 - elapsed: 3364us
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us
考慮しない結果のために1連結(JITませんでしたいいつもの仕事も10concatenationsの刑に関;千concatenations、その差は大きい。
教訓から非常に速く実験(容易に再現可能上記のコード):は絶対に使わないでください +=
連結の文字列としても非常に基本的な場合の数concatenationsが必要として、新しい文字列が高く、圧力のGC)があります。
あなたはおそらくできるようにtoStringメソッドが読み取り可能にしてください!
あなたがの(はい、これはプロファイリングを意味する):)それは重要なリソースを消費するように私にはの証明できる場合は、私の本の中で、このための唯一の例外はあります
は、Java 5コンパイラは、Javaの以前のバージョンで使用された手書き「のStringBuffer」アプローチよりも高速なコードを生成していることに注意してください。あなたは「+」を使用している場合はこれと将来の拡張機能が無料で付属します。
のStringBuilderを使用して、まだ現在のコンパイラで必要とされているかどうかをいくつかの議論があるようです。だから私は、私は経験の私の2セントを与えるでしょうと思いました。
私は10KレコードのJDBC
結果セットを持っている(はい、私は1つのバッチでそれらのすべてを必要とします。)+演算子を使用すると、Java 1.8
で私のマシンで約5分かかります。 stringBuilder.append("")
を使用すると、同じクエリのための秒未満かかります。
だから、違いは巨大です。ループStringBuilder
内部はるかに高速です。
例を参照してください。
//java8
static void main(String[] args) {
case1();//str.concat
case2();//+=
case3();//StringBuilder
}
static void case1() {
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str = str.concat(UUID.randomUUID()+"---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}
static void case2() {
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str+=UUID.randomUUID()+"---";
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}
static void case3() {
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
StringBuilder str = new StringBuilder("");
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str.append(UUID.randomUUID()+"---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}
static void saveTime(List<Long> executionTimes, long startTime) {
executionTimes.add(System.currentTimeMillis()-startTime);
if(executionTimes.size()%CALC_AVG_EVERY == 0) {
out.println("average time for "+executionTimes.size()+" concatenations: "+
NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(()->0))+
" ms avg");
executionTimes.clear();
}
}
出力:
平均時間10000concatenations:0.096ms avg
平均時間10000concatenations:0.185ms avg
平均時間10000concatenations:0.327ms avg
平均時間10000concatenations:0.501ms avg
平均時間10000concatenations:0.656ms avg
作成した文字列の長さ:1950000に 17745ms
平均時間10000concatenations:0.21ms avg
平均時間10000concatenations:0.652ms avg
平均時間10000concatenations:1.129ms avg
平均時間10000concatenations:1.727ms avg
平均時間10000concatenations:2.302ms avg
作成した文字列の長さ:1950000に 60279ms
平均時間10000concatenations:0.002ms avg
平均時間10000concatenations:0.002ms avg
平均時間10000concatenations:0.002ms avg
平均時間10000concatenations:0.002ms avg
平均時間10000concatenations:0.002ms avg
作成した文字列の長さ:1950000に 100ms
として、文字列の長さでどのようなプレーヤーで、連結します。
そこに、 StringBuilder
が必要。
については、連結: UUID.randomUUID()+"---"
, なので本当に影響を与えます。
P.S.: とは思わない するために使用されStringBuilder Java は本当に重複している。
この質問につ toString()
ほとんどの時は行いませんconcatenationsの大きな文字列です。
性能的に文字列の連結用に'+'るのは事実でしているという新しいコピー文字列から文字列は不変でjava.この特に重要でない場合は連結では非常に頻繁なので、例えば:内部ループを実行します。以下は私の考えを示唆ようとするというもの:
一般規則:
- 一つの文字列配置を用い、文字列の連結に影響はありません。
- だループに大きなブロックの文字データは、StringBuffer.
- 利用+=文字列は必ずより非効率化をStringBufferを入力してくださいリング警鐘が一定の場合の最適化が得られます無視できるとの比較を読みやすさが課題でもあったので、ご利用お客様共通です。
こちらは 素敵ジョンSkeetブログ 周辺のこの話題です。
はできるがんに対して繰り返し処理を実行するコレクションを使用StringBuilder ールする必要がありますチェック Apache Commons Lang や StringUtils.join() (味)?
を問わず、できることをStringBuildersおよびループのためにどのように 百万分の ます。
私はパフォーマンスを比較するために4つの異なるアプローチを比較しました。私は正確にGCに何が起こるか分かりませんが、私にとって重要なのは時間です。コンパイラはhere.I window8.1プラットフォームの下でjdk1.8.0_45使用される重要な要因である。
concatWithPlusOperator = 8
concatWithBuilder = 130
concatWithConcat = 127
concatStringFormat = 3737
concatWithBuilder2 = 46
public class StringConcatenationBenchmark {
private static final int MAX_LOOP_COUNT = 1000000;
public static void main(String[] args) {
int loopCount = 0;
long t1 = System.currentTimeMillis();
while (loopCount < MAX_LOOP_COUNT) {
concatWithPlusOperator();
loopCount++;
}
long t2 = System.currentTimeMillis();
System.out.println("concatWithPlusOperator = " + (t2 - t1));
long t3 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatWithBuilder();
loopCount++;
}
long t4 = System.currentTimeMillis();
System.out.println("concatWithBuilder = " + (t4 - t3));
long t5 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatWithConcat();
loopCount++;
}
long t6 = System.currentTimeMillis();
System.out.println("concatWithConcat = " + (t6 - t5));
long t7 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatStringFormat();
loopCount++;
}
long t8 = System.currentTimeMillis();
System.out.println("concatStringFormat = " + (t8 - t7));
long t9 = System.currentTimeMillis();
loopCount = 0;
while (loopCount < MAX_LOOP_COUNT) {
concatWithBuilder2();
loopCount++;
}
long t10 = System.currentTimeMillis();
System.out.println("concatWithBuilder2 = " + (t10 - t9));
}
private static void concatStringFormat() {
String s = String.format("%s %s %s %s ", "String", "String", "String", "String");
}
private static void concatWithConcat() {
String s = "String".concat("String").concat("String").concat("String");
}
private static void concatWithBuilder() {
StringBuilder builder=new StringBuilder("String");
builder.append("String").append("String").append("String");
String s = builder.toString();
}
private static void concatWithBuilder2() {
String s = new StringBuilder("String").append("String").append("String").append("String").toString();
}
private static void concatWithPlusOperator() {
String s = "String" + "String" + "String" + "String";
}
}
これがそのチJava8
- 使用文字列の連結
使用StringBuilder
long time1 = System.currentTimeMillis(); usingStringConcatenation(100000); System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms"); time1 = System.currentTimeMillis(); usingStringBuilder(100000); System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms"); private static void usingStringBuilder(int n) { StringBuilder str = new StringBuilder(); for(int i=0;i<n;i++) str.append("myBigString"); } private static void usingStringConcatenation(int n) { String str = ""; for(int i=0;i<n;i++) str+="myBigString"; }
で悪夢をご利用の場合は文字列の連結のための多数の文字列です。
usingStringConcatenation 29321 ms
usingStringBuilder 2 ms
していくべきだと感じていますとStringBuilderに追加。その理由は
文字列の連結新しいstringオブジェクト毎時として文字列は変更不能なオブジェクト)を作成します3。
文字列ビルダーだけのオブジェクトを作成[StringBuilderがmuttable]のさらなる文字列が追加されます。
私が使用することを好むような単純な文字列の場合
"string".concat("string").concat("string");
ために、私は、文字列を構築する好ましい方法はStringBuilderの文字列#1 CONCAT()、その後、オーバーロード+演算子を使用していると言うことになります。ちょうど+演算子を使用してのような大規模な文字列を操作するときのStringBuilderは、大幅な性能の増加であることは、パフォーマンスの大幅な低下(文字列サイズの増加に伴って指数関数的に大きい減少)です。 .concatを()を使用しての一つの問題は、それがNullPointerExceptionsがを投げることができるということです。