Dize birleştirme: concat () ve “+” operatörü


499

Dize a ve b varsayarsak:

a += b
a = a.concat(b)

Kaputun altında, aynı şey mi?

Burada concat referans olarak ayrıştırılmıştır. Bunun +ne yaptığını görmek için operatörü de kaynak koduna dönüştürmek istiyorum .

public String concat(String s) {

    int i = s.length();
    if (i == 0) {
        return this;
    }
    else {
        char ac[] = new char[count + i];
        getChars(0, count, ac, 0);
        s.getChars(0, i, ac, count);
        return new String(0, count + i, ac);
    }
}


3
+Ayrıştırılabileceğinden emin değilim .
Galen Nare

1
Bir Java sınıf dosyasını sökmek için javap komutunu kullanın .
Hot Licks

'Değişmezlik' nedeniyle muhtemelen StringBufferveya StringBuilder- (bu nedenle daha hızlı güvenli olmayan iplik kullanmalısınız)
Ujjwal Singh

Yanıtlar:


560

Hayır, pek değil.

İlk olarak, anlambilimde ufak bir fark vardır. Eğer abir null, daha sonra a.concat(b)bir atar NullPointerExceptionama a+=borijinal değerini ele alacağız ao sanki null. Ayrıca, concat()yöntem yalnızca Stringdeğerleri kabul ederken, +operatör bağımsız değişkeni sessizce bir String'e dönüştürür ( toString()nesneler için yöntemi kullanarak ). Dolayısıyla concat()yöntem, kabul ettiği şeyde daha katıdır.

Kaputun altına bakmak için, a += b;

public class Concat {
    String cat(String a, String b) {
        a += b;
        return a;
    }
}

Şimdi ile sökün javap -c(Sun JDK'ya dahil). Aşağıdakileri içeren bir giriş görmelisiniz:

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

Yani, a += beşdeğeri

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();

concatYöntemi daha hızlı olmalıdır. Ancak, daha fazla dizeyle StringBuilderyöntem en azından performans açısından kazanır.

StringVe StringBuilder(ve paket-özel taban sınıfının) kaynak kodu Sun JDK'nın src.zip dosyasında bulunur. Bir char dizisi oluşturduğunuzu (gerekirse yeniden boyutlandırdığınızı) ve ardından finali oluşturduğunuzda attığınızı görebilirsiniz String. Pratikte bellek tahsisi şaşırtıcı derecede hızlıdır.

Güncelleme: Pawel Adamski'nin belirttiği gibi, daha yeni HotSpot'ta performans değişti. javachala tam olarak aynı kodu üretir, ancak bytecode derleyicisi hile yapar. Tüm kod gövdesi atıldığından basit testler tamamen başarısız olur. Toplama System.identityHashCode(değil String.hashCode), StringBufferkodun hafif bir avantajı olduğunu gösterir. Bir sonraki güncelleme yayınlandığında veya farklı bir JVM kullanıyorsanız değişebilir. Gönderen @lukaseder , HotSpot JVM intrinsics listesi .


4
@HyperLink Kodu kullanan javap -cderlenmiş bir sınıfta kullanarak kodu görebilirsiniz . (Ah, cevapta olduğu gibi. Sadece zor olmamalı baytkodun sökülmesini yorumlamanız gerekiyor.)
Tom Hawtin -

1
Tek tek bayt kodlarını anlamak için JVM spesifikasyonlarına başvurabilirsiniz . Referans vermek istediğiniz şeyler bölüm 6'dadır. Biraz anlaşılmaz, ancak bunun özünü kolayca alabilirsiniz.
Hot Licks

1
Java derleyicisinin StringBuilderiki dizeye katılırken neden kullandığını merak ediyorum. Eğer Stringdahil statik yöntemler dört telli ya da tüm şeritlerinin kadar bitiştirmek String[], kod iki nesne tahsisleri (sonuç dört dizeleri kadar eklemek olabilir Stringve destek malzemesinin char[](ne bir yedekli) ve üç tahsisleri ile şeritlerinin herhangi bir sayıda String[]sonuç String, ve destek char[]sadece birinci olmak İhtiyaç fazlası ile,). Olduğu gibi, kullanarak StringBuilderiradesini en iyi dört tahsisleri gerektirir ve iki kez her karakteri kopyalama gerektirecektir.
supercat

Bu ifade, a + = b. Bu şu anlama gelmiyor: a = a + b?
en saygıdeğer efendim

3
Bu cevabın oluşturulmasından bu yana işler değişti. Lütfen aşağıdaki cevabımı okuyun.
Paweł Adamski

90

Niyaz doğrudur, ancak özel + operatörün Java derleyicisi tarafından daha verimli bir şeye dönüştürülebileceğini de belirtmek gerekir. Java, iş parçacığı için güvenli olmayan, değiştirilebilir bir String'i temsil eden bir StringBuilder sınıfına sahiptir. Bir dizi String birleşimi gerçekleştirirken, Java derleyici sessizce dönüştürür

String a = b + c + d;

içine

String a = new StringBuilder(b).append(c).append(d).toString();

hangi büyük dizeler için önemli ölçüde daha verimlidir. Bildiğim kadarıyla, concat yöntemini kullandığınızda bu olmaz.

Ancak, concat yöntemi boş bir String'i mevcut bir String ile birleştirirken daha etkilidir. Bu durumda, JVM'nin yeni bir String nesnesi oluşturması gerekmez ve sadece varolan nesneyi döndürebilir. Bunu onaylamak için concat belgelerine bakın .

Verimlilik konusunda aşırı endişeleriniz varsa, boş dizeleri birleştirirken concat yöntemini kullanmanız ve aksi halde + kullanmanız gerekir. Ancak, performans farkı ihmal edilebilir olmalı ve muhtemelen bu konuda endişelenmemelisiniz.


concat infact bunu yapmaz.
Gönderi

10
infact yapar. Concat kodunuzun ilk satırlarına bakın. Concat ile ilgili sorun, her zaman yeni bir String () oluşturmasıdır
Marcio Aguiar

2
@MarcioAguiar: belki + her zaman yeni bir şey oluşturduğunu kastediyorsunuz String- dediğiniz gibi concatboş bir yere katlandığınızda bir istisnası var String.
Blaisorblade

45

@Marcio ile benzer bir test yaptım ama bunun yerine şu döngüde:

String c = a;
for (long i = 0; i < 100000L; i++) {
    c = c.concat(b); // make sure javac cannot skip the loop
    // using c += b for the alternative
}

Sadece iyi bir ölçü için, ben de girdim StringBuilder.append(). Her test 10 kez yapıldı ve her çalışma için 100k tekrar yapıldı. Sonuçlar burada:

  • StringBuildereller aşağı kazanır. Saat süresi sonucu çoğu koşu için 0 oldu ve en uzun süre 16 ms sürdü.
  • a += b her çalışma için yaklaşık 40000ms (40s) alır.
  • concat sadece çalışma başına 10000ms (10s) gerektirir.

İç a += bkısımları görmek veya henüz profiler aracılığıyla çalıştırmak için sınıfı çözmedim, ancak çoğu zaman yeni nesneler oluşturmak StringBuilderve daha sonra bunları dönüştürmek için zaman harcadığından şüpheleniyorum String.


4
Nesne oluşturma süresi gerçekten önemlidir. Bu nedenle birçok durumda StringBuilder + 'ın arkasından yararlanmak yerine StringBuilder'ı doğrudan kullanırız.
coolcfan

1
@coolcfan: +İki dize için kullanıldığında, kullanımın StringBuilderolması gerekenden daha iyi olduğu durumlar var String.valueOf(s1).concat(s2)mı? Derleyicilerin ikincisini neden kullanmamaları veya boş olmadığı bilinen valueOfdurumlarda çağrıyı atlamamasıyla ilgili herhangi bir fikir s1?
supercat

1
@supercat üzgünüm bilmiyorum. Belki de bu şekerin arkasında olan insanlar buna cevap verecek en iyi kişilerdir.
coolcfan

25

Cevapların çoğu 2008'den geliyor. Görünüşe göre işler zaman içinde değişti. JMH ile yapılan en son karşılaştırmalar, Java 8'de +yaklaşık iki kat daha hızlı olduğunu gösteriyor concat.

Karşılaştırmam:

@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State2 {
        public String a = "abc";
        public String b = "xyz";
    }

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State3 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
    }


    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State4 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
        public String d = "!@#";
    }

    @Benchmark
    public void plus_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b);
    }

    @Benchmark
    public void plus_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c);
    }

    @Benchmark
    public void plus_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c+state.d);
    }

    @Benchmark
    public void stringbuilder_2(State2 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
    }

    @Benchmark
    public void stringbuilder_3(State3 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
    }

    @Benchmark
    public void stringbuilder_4(State4 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
    }

    @Benchmark
    public void concat_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b));
    }

    @Benchmark
    public void concat_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c)));
    }


    @Benchmark
    public void concat_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
    }
}

Sonuçlar:

Benchmark                             Mode  Cnt         Score         Error  Units
StringConcatenation.concat_2         thrpt   50  24908871.258 ± 1011269.986  ops/s
StringConcatenation.concat_3         thrpt   50  14228193.918 ±  466892.616  ops/s
StringConcatenation.concat_4         thrpt   50   9845069.776 ±  350532.591  ops/s
StringConcatenation.plus_2           thrpt   50  38999662.292 ± 8107397.316  ops/s
StringConcatenation.plus_3           thrpt   50  34985722.222 ± 5442660.250  ops/s
StringConcatenation.plus_4           thrpt   50  31910376.337 ± 2861001.162  ops/s
StringConcatenation.stringbuilder_2  thrpt   50  40472888.230 ± 9011210.632  ops/s
StringConcatenation.stringbuilder_3  thrpt   50  33902151.616 ± 5449026.680  ops/s
StringConcatenation.stringbuilder_4  thrpt   50  29220479.267 ± 3435315.681  ops/s

Java neden Stringöğeleri bir araya getirerek bir dize oluşturmak için statik bir işlev içermediğini merak ediyorum String[]. Kullanma +yapılandırılması ve daha sonra terk gerektiren böyle bir fonksiyonu ile 8 dizeleri bitiştirmek için String[8]bir kullanırken, ama bu terk inşa edilmesi gerekir sadece nesne olacaktır StringBuilderyapılandırılması ve terk gerektirecektir StringBuilderörneği ve en az bir char[]destek deposu.
supercat

@supercat String.join()Java 8'de java.util.StringJoinersınıfın etrafında hızlı sözdizimi sarmalayıcıları olarak bazı statik yöntemler eklendi .
Ti Strga

@TiStrga: +Bu işlevleri kullanmak için kullanımı değiştirildi mi?
supercat

@supercat Bu ikili geriye dönük uyumluluğu bozar, bu yüzden hayır. Bu sizin "Dize statik bir fonksiyonu dahil neden hiç" yorumuna cevapta ibaretti: şimdi ise böyle bir fonksiyonu. Teklifinizin geri kalanı ( +kullanmak için yeniden düzenleme), Java geliştiricilerinin ne yazık ki değiştirmek istediklerinden daha fazlasını gerektirecektir.
Ti Strga

@TiStrga: Bir Java bayt kodu dosyasının "X işlevi varsa, çağırın; aksi halde başka bir şey yapın" ifadesini sınıf yükleme sürecinde çözülebilecek bir şekilde gösterebilir mi? Java'nın statik yöntemine zincir oluşturabilecek statik bir yöntemle kod üretmek veya yoksa bir stringbuilder kullanmak en uygun çözüm olarak görünecektir.
SuperCat

22

Tom, + operatörünün tam olarak ne yaptığını anlatmakta haklıdır. Geçici oluşturur StringBuilder, parçaları ekler ve bitirir toString().

Ancak, şimdiye kadarki tüm cevaplar HotSpot çalışma zamanı optimizasyonlarının etkilerini görmezden geliyor. Özellikle, bu geçici işlemler ortak bir örüntü olarak tanınır ve çalışma zamanında daha verimli makine kodu ile değiştirilir.

@marcio: Bir mikro kıyaslama yarattınız ; modern JVM'lerle bu profil kodunun geçerli bir yolu değildir.

Çalışma zamanı optimizasyonunun önemli olmasının nedeni, koddaki bu farklılıkların çoğunun - nesne oluşturma da dahil olmak üzere - HotSpot çalışmaya başladıktan sonra tamamen farklı olmasıdır. Kesin olarak bilmenin tek yolu kodunuzu yerinde profillendirmektir .

Son olarak, tüm bu yöntemler aslında inanılmaz derecede hızlıdır. Bu erken bir optimizasyon olabilir. Dizeleri çok fazla birleştiren bir kodunuz varsa, maksimum hızı elde etmenin muhtemelen seçtiğiniz operatörlerle ve bunun yerine kullandığınız algoritmayla ilgisi yoktur!


"Bu geçici operasyonlar" ile kastedilen "yığın" nesneleri doğru olduğu yerde yığmak için kaçış analizi kullanımı demek. HotSpot'ta kaçış analizi mevcut olsa da (bazı senkronizasyonu kaldırmak için yararlıdır), inanmıyorum, yazma sırasında, u
Tom Hawtin - tackline

21

Bazı basit testlere ne dersiniz? Aşağıdaki kodu kullandınız:

long start = System.currentTimeMillis();

String a = "a";

String b = "b";

for (int i = 0; i < 10000000; i++) { //ten million times
     String c = a.concat(b);
}

long end = System.currentTimeMillis();

System.out.println(end - start);
  • "a + b"Versiyon idam 2500ms .
  • a.concat(b)İdam 1200ms .

Birkaç kez test edildi. concat()Versiyon yürütme ortalama zamanın yarısını aldı.

Bu sonuç beni şaşırttı çünkü concat()yöntem her zaman yeni bir dize oluşturur (" new String(result)" döndürür .

String a = new String("a") // more than 20 times slower than String a = "a"

Derleyici neden her zaman aynı dizeyle sonuçlandığını bilerek "a + b" kodunda dize oluşturmayı optimize edemedi? Yeni bir dize oluşturulmasını önleyebilir. Yukarıdaki ifadeye inanmıyorsanız, kendinizi test edin.


Java jdk1.8.0_241 kodunuzu test ettim, Benim için "a + b" kodu optimize edilmiş sonuçlar veriyor. Concat () ile: 203ms ve "+" ile: 113ms . Sanırım önceki sürümde bu optimize değildi.
Akki

6

Temel olarak, + ve concatyöntem arasında iki önemli fark vardır .

  1. Concat yöntemini kullanıyorsanız, yalnızca + işleci durumunda dizeleri birleştirebilirsiniz, dizeyi herhangi bir veri türüyle de birleştirebilirsiniz.

    Örneğin:

    String s = 10 + "Hello";

    Bu durumda, çıktı 10 Merhaba olmalıdır .

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);

    Yukarıdaki durumda zorunlu iki dize sağlamanız gerekir.

  2. + Ve concat arasındaki ikinci ve ana fark şudur:

    Durum 1: Aynı dizeleri concat operatörü ile bu şekilde kapattığımı varsayalım

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);

    Bu durumda havuzda oluşturulan toplam nesne sayısı 7'dir:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy

    Durum 2:

    Şimdi aynı dizeleri + operatörü aracılığıyla birleştireceğim

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);

    Yukarıdaki durumda, oluşturulan toplam nesne sayısı sadece 5'tir.

    Aslında dizeleri + işleci ile bitirdiğimizde, aynı görevi gerçekleştirmek için bir StringBuffer sınıfını korur: -

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);

    Bu şekilde sadece beş nesne yaratacaktır.

Yani çocuklar + ve concat yöntemi arasındaki temel farklar bunlar . Zevk almak :)


Benim sevgili, çok iyi biliyorum herhangi bir dize değişmezi String havuzunda saklanan bir String nesnesi olarak davranır.Bu nedenle 4 dize değişmezleri var. Açıkçası havuzda en az 4 nesne oluşturulmalıdır.
Deepak Sharma

1
Ben öyle düşünmüyorum: String s="I"+"am"+"good"+"boy"; String s2 = "go".concat("od"); System.out.println(s2 == s2.intern());baskılar true, "good"aramadan önce dize havuzunda değildi anlamına gelirintern()
fabian

Ben sadece bu çizgi hakkında konuşuyorum String s = "I" + "am" + "good" + "boy"; Bu durumda, dize değişmezlerinin tümü bir havuzda tutulur, bu nedenle havuzda 4 nesne oluşturulmalıdır.
Deepak Sharma

4

Tamlık uğruna, '+' operatörünün tanımının JLS SE8 15.18.1'de bulunabileceğini eklemek istedim :

Dize türünden yalnızca bir işlenen ifadesi varsa, çalışma zamanında bir dize üretmek için diğer işlenende dize dönüştürme (§5.1.11) gerçekleştirilir.

Dize birleşiminin sonucu, iki işlenen dizenin birleşimi olan bir String nesnesine bir başvurudur. Sol taraftaki işlenenin karakterleri, yeni oluşturulan dizede sağ taraftaki işlenenin karakterlerinden önce gelir.

İfade sabit bir ifade olmadığı sürece String nesnesi yeni oluşturulur (§12.5).

Uygulama hakkında JLS şunları söylüyor:

Bir uygulama, bir ara String nesnesi oluşturmaktan ve sonra atmaktan kaçınmak için bir adımda dönüştürme ve birleştirme işlemini seçebilir. Yinelenen dize birleştirme performansını artırmak için, bir Java derleyicisi, bir ifadenin değerlendirilmesi ile oluşturulan ara String nesnelerinin sayısını azaltmak için StringBuffer sınıfını veya benzer bir tekniği kullanabilir.

İlkel türler için bir uygulama, doğrudan ilkel türden dizeye dönüştürerek bir sarıcı nesnenin oluşturulmasını da optimize edebilir.

Bu yüzden 'bir Java derleyicisi StringBuffer sınıfını veya benzer bir tekniği azaltmak için kullanabilir' den bakıldığında, farklı derleyiciler farklı bayt kodu üretebilir.


2

+ Operatör bir dizi ve bir şerit, char tamsayı, çift ya da yüzer bir veri türü değeri arasında çalışabilir. Birleştirme işleminden önce değeri dize temsiline dönüştürür.

Concat operatörü yalnızca ve dizeleri ile yapılabilir. Veri türü uyumluluğunu kontrol eder ve eşleşmezlerse bir hata atar.

Bunun dışında, sağladığınız kod aynı şeyleri yapar.


2

Ben öyle düşünmüyorum.

a.concat(b)String'de uygulanmaktadır ve uygulamanın java makinelerinden bu yana çok fazla değişmediğini düşünüyorum. +Operasyon uygulaması Java sürümü ve derleyici bağlıdır. Şu anda işlemi mümkün olduğunca hızlı yapmak için +uygulanmaktadır StringBuffer. Belki gelecekte bu değişecektir. +Dizelerde java işleminin önceki sürümlerinde ara sonuçlar verdiği için çok daha yavaştı.

Sanırım bu +=kullanılarak uygulandı +ve benzer şekilde optimize edildi.


7
"Şu anda + StringBuffer kullanılarak uygulanır" False It's StringBuilder. StringBuffer, StringBuilder'ın threadsafe impl.
Frederic Morin

1
Java 1.5'ten önce StringBuffer'dı, çünkü StringBuilder'ın ilk tanıtıldığı sürüm buydu.
ccpizza

0

+ Kullanırken, dize uzunluğu arttıkça hız azalır, ancak concat kullanılırken hız daha kararlı olur ve en iyi seçenek bunu yapmak için sabit hıza sahip StringBuilder sınıfını kullanmaktır.

Sanırım nedenini anlayabilirsiniz. Ancak uzun dizeler oluşturmanın en iyi yolu StringBuilder () ve append () kullanmaktır, her iki hız da kabul edilemez.


1
+ işlecini kullanmak StringBuilder'ı kullanmaya eşdeğerdir ( docs.oracle.com/javase/specs/jls/se8/html/… )
ihebiheb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.