Rangkaian string: concat () vs operator "+"

499

Dengan asumsi String a dan b:

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

Di bawah tenda, apakah mereka hal yang sama?

Di sini concat didekompilasi sebagai referensi. Saya ingin dapat mendekompilasi +operator juga untuk melihat apa yang dilakukannya.

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);
    }
}
shsteimer
sumber
3
kemungkinan duplikat dari penggabungan StringBuilder vs String di toString () di Jawa
Greg Mattes
3
Saya tidak yakin +bisa didekompilasi.
Galen Nare
1
Gunakan javap untuk membongkar file kelas Java.
Hot Licks
Karena 'kekekalan' Anda mungkin harus menggunakan StringBufferatau StringBuilder- (utas tidak aman lebih cepat, sebagai gantinya
Ujjwal Singh

Jawaban:

560

Tidak, tidak cukup.

Pertama, ada sedikit perbedaan dalam semantik. Jika aini null, maka a.concat(b)melemparkan NullPointerExceptiontetapi a+=bakan memperlakukan nilai asli aseolah-olah itu null. Selain itu, concat()metode ini hanya menerima Stringnilai sementara +operator akan secara diam-diam mengubah argumen menjadi String (menggunakan toString()metode untuk objek). Sehinggaconcat() metode ini lebih ketat dalam apa yang diterimanya.

Untuk melihat di bawah tenda, tulis kelas sederhana dengan a += b;

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

Sekarang dibongkar dengan javap -c(termasuk dalam Sun JDK). Anda akan melihat daftar termasuk:

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

Jadi, a += bsama dengan

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

The concatmetode harus lebih cepat. Namun, dengan lebih banyak stringStringBuilder metode ini menang, setidaknya dalam hal kinerja.

Kode sumber Stringdan StringBuilder(dan kelas dasar paket-privasinya) tersedia di src.zip dari Sun JDK. Anda dapat melihat bahwa Anda sedang membangun array char (mengubah ukuran sesuai kebutuhan) dan kemudian membuangnya ketika Anda membuat final String. Dalam praktiknya alokasi memori sangat cepat.

Pembaruan: Seperti catatan Pawel Adamski, kinerja telah berubah di HotSpot yang lebih baru. javacmasih menghasilkan kode yang persis sama, tetapi kode curang bytecode. Pengujian sederhana sepenuhnya gagal karena seluruh badan kode dibuang. Penjumlahan System.identityHashCode(tidak String.hashCode) menunjukkan StringBufferkode memiliki sedikit keuntungan. Dapat berubah ketika pembaruan berikutnya dirilis, atau jika Anda menggunakan JVM yang berbeda. Dari @lukaseder , daftar intrinsik HotSpot JVM .

Tom Hawtin - tackline
sumber
4
@HyperLink Anda dapat melihat kode yang digunakan javap -cpada kelas terkompilasi yang menggunakannya. (Oh, seperti dalam jawabannya. Anda hanya perlu menafsirkan pembongkaran bytecode, yang seharusnya tidak terlalu sulit.)
Tom Hawtin - tackline
1
Anda dapat berkonsultasi dengan spesifikasi JVM untuk memahami bytecode individu. Hal-hal yang ingin Anda rujuk ada di bab 6. Agak tidak jelas, tetapi Anda bisa mendapatkan intinya dengan mudah.
Hot Licks
1
Saya bertanya-tanya mengapa kompiler Java menggunakan StringBuilderbahkan ketika bergabung dengan dua string? Jika Stringmenyertakan metode statis untuk menggabungkan hingga empat string, atau semua string dalam String[], kode dapat menambahkan hingga empat string dengan alokasi objek dua (hasil Stringdan dukungannya char[], tidak ada yang berlebihan), dan sejumlah string dengan alokasi tiga ( itu String[], hasilnya String, dan dukungan char[], dengan hanya yang pertama yang berlebihan). Karena itu, menggunakan StringBuilderkehendak terbaik membutuhkan empat alokasi, dan akan membutuhkan menyalin setiap karakter dua kali.
supercat
Ekspresi itu, a + = b. Bukankah itu berarti: a = a + b?
Yang Mulia
3
Banyak hal telah berubah sejak saat jawaban ini dibuat. Silakan baca jawaban saya di bawah.
Paweł Adamski
90

Niyaz benar, tetapi perlu juga dicatat bahwa operator + khusus dapat dikonversi menjadi sesuatu yang lebih efisien oleh kompiler Java. Java memiliki kelas StringBuilder yang mewakili String yang tidak dapat diubah, aman, dapat diubah. Saat melakukan sekelompok rangkaian String, kompiler Java secara diam-diam mengonversi

String a = b + c + d;

ke

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

yang untuk string besar secara signifikan lebih efisien. Sejauh yang saya tahu, ini tidak terjadi ketika Anda menggunakan metode concat.

Namun, metode concat lebih efisien ketika menggabungkan String kosong ke String yang ada. Dalam hal ini, JVM tidak perlu membuat objek String baru dan cukup mengembalikan yang sudah ada. Lihat dokumentasi concat untuk mengonfirmasi hal ini.

Jadi, jika Anda sangat memperhatikan efisiensi maka Anda harus menggunakan metode concat saat menggabungkan string-string yang mungkin kosong, dan gunakan + sebaliknya. Namun, perbedaan kinerja harus diabaikan dan Anda mungkin tidak perlu khawatir tentang hal ini.

Eli Courtwright
sumber
Infact concat tidak melakukan itu. Saya telah mengedit posting saya dengan dekompilasi metode concat
shsteimer
10
Infact itu. Lihatlah baris pertama dari kode concat Anda. Masalah dengan concat adalah selalu menghasilkan String baru ()
Marcio Aguiar
2
@MarcioAguiar: mungkin maksud Anda + selalu menghasilkan yang baru String- seperti yang Anda katakan, concatmemiliki satu pengecualian saat Anda membuat kosong String.
Blaisorblade
45

Saya menjalankan tes serupa dengan @marcio tetapi dengan loop berikut sebagai gantinya:

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
}

Hanya untuk ukuran yang baik, saya juga melempar StringBuilder.append(). Setiap tes dijalankan 10 kali, dengan 100rb repetisi untuk setiap putaran. Inilah hasilnya:

  • StringBuildermenang telak. Hasil jam waktu adalah 0 untuk sebagian besar berjalan, dan yang paling lama mengambil 16ms.
  • a += b membutuhkan sekitar 40000 ms (40 detik) untuk setiap proses.
  • concat hanya membutuhkan 10000ms (10s) per run.

Saya belum mendekompilasi kelas untuk melihat internal atau menjalankannya melalui profiler, tapi saya curiga a += bmenghabiskan banyak waktu membuat objek baru StringBuilderdan kemudian mengubahnya kembali String.

ckpwong
sumber
4
Waktu pembuatan objek sangat penting. Itu sebabnya dalam banyak situasi kami menggunakan StringBuilder secara langsung daripada mengambil keuntungan dari StringBuilder di belakang +.
coolcfan
1
@coolcfan: Kapan +digunakan untuk dua string, apakah ada kasus di mana menggunakan StringBuilderlebih baik daripada yang akan terjadi String.valueOf(s1).concat(s2)? Adakah gagasan mengapa kompiler tidak akan menggunakan yang terakhir [atau mengabaikan valueOfpanggilan jika s1tidak diketahui null]?
supercat
1
@ supercat maaf saya tidak tahu. Mungkin orang yang berada di belakang gula ini adalah yang terbaik untuk menjawab ini.
coolcfan
25

Sebagian besar jawaban di sini berasal dari 2008. Tampaknya banyak hal telah berubah dari waktu ke waktu. Benchmark terbaru saya yang dibuat dengan JMH menunjukkan bahwa di Java 8 +sekitar dua kali lebih cepat daripada concat.

Tolok ukur saya:

@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))));
    }
}

Hasil:

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
Paweł Adamski
sumber
Saya bertanya-tanya mengapa Java Stringtidak pernah memasukkan fungsi statis untuk membentuk string dengan merangkai unsur-unsur a String[]. Menggunakan +untuk menggabungkan 8 string menggunakan fungsi seperti itu akan membutuhkan pembangunan dan kemudian ditinggalkan String[8], tetapi itu akan menjadi satu-satunya objek yang perlu dibangun ditinggalkan, sementara menggunakan StringBuildermembutuhkan akan membangun dan meninggalkan StringBuildercontoh dan setidaknya satu char[]toko dukungan.
supercat
@supercat Beberapa String.join()metode statis ditambahkan di Java 8, seperti pembungkus sintaks cepat di sekitar java.util.StringJoinerkelas.
Ti Strga
@TiStrga: Apakah penanganan telah +diubah untuk menggunakan fungsi seperti itu?
supercat
@supercat Itu akan merusak kompatibilitas mundur biner, jadi tidak. Itu hanya di balasan untuk Anda "mengapa String tidak pernah disertakan fungsi statis" komentar: sekarang ada adalah fungsi seperti. Sisa proposal Anda (refactoring +untuk menggunakannya) akan membutuhkan lebih dari apa yang ingin diubah oleh para dev Jawa.
Ti Strga
@TiStrga: Apakah ada cara file bytecode Java dapat mengindikasikan "Jika fungsi X tersedia, panggil saja; kalau tidak lakukan sesuatu yang lain" dengan cara yang bisa diselesaikan dalam proses memuat kelas? Membuat kode dengan metode statis yang dapat berantai dengan metode statis Java atau menggunakan stringbuilder jika itu tidak tersedia akan tampak solusi optimal.
supercat
22

Tom benar dalam menggambarkan apa yang dilakukan operator +. Itu menciptakan sementara StringBuilder, menambahkan bagian-bagian, dan selesai dengan toString().

Namun, semua jawaban sejauh ini mengabaikan efek dari optimasi runtime HotSpot. Secara khusus, operasi sementara ini diakui sebagai pola umum dan diganti dengan kode mesin yang lebih efisien saat dijalankan.

@marcio: Anda telah membuat tolok ukur mikro ; dengan JVM modern ini bukan cara yang valid untuk kode profil.

Alasan mengapa run-time optimasi penting adalah karena banyak perbedaan dalam kode ini - bahkan termasuk pembuatan objek - benar-benar berbeda ketika HotSpot berjalan. Satu-satunya cara untuk mengetahui dengan pasti adalah membuat profil kode Anda di tempat .

Akhirnya, semua metode ini sebenarnya sangat cepat. Ini mungkin merupakan kasus optimasi prematur. Jika Anda memiliki kode yang banyak merangkai string, cara untuk mendapatkan kecepatan maksimum mungkin tidak ada hubungannya dengan operator mana yang Anda pilih dan alih-alih algoritma yang Anda gunakan!

Jason Cohen
sumber
Saya kira dengan "operasi sementara ini" yang Anda maksud adalah penggunaan analisis pelarian untuk mengalokasikan objek "tumpukan" pada tumpukan yang terbukti benar. Meskipun analisis melarikan diri ada di HotSpot (berguna untuk menghapus beberapa sinkronisasi), saya tidak percaya, pada saat penulisan, u
Tom Hawtin - tackline
21

Bagaimana dengan beberapa pengujian sederhana? Gunakan kode di bawah ini:

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);
  • The "a + b"Versi dieksekusi di 2500ms .
  • The a.concat(b)dieksekusi di 1200ms .

Diuji beberapa kali. The concat()eksekusi Versi mengambil setengah dari waktu rata-rata.

Hasil ini mengejutkan saya karena concat()metode ini selalu menciptakan string baru (mengembalikan " new String(result)". Sudah diketahui bahwa:

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

Mengapa kompiler tidak dapat mengoptimalkan pembuatan string dalam kode "a + b", mengetahui bahwa selalu menghasilkan string yang sama? Itu bisa menghindari pembuatan string baru. Jika Anda tidak percaya dengan pernyataan di atas, ujilah diri Anda.

Marcio Aguiar
sumber
Saya menguji pada java jdk1.8.0_241 kode Anda, bagi saya kode "a + b" memberikan hasil yang optimal. Dengan concat (): 203ms dan dengan "+": 113ms . Saya kira dalam rilis sebelumnya itu tidak dioptimalkan.
Akki
6

Pada dasarnya, ada dua perbedaan penting antara + dan concatmetode.

  1. Jika Anda menggunakan metode concat maka Anda hanya akan dapat menggabungkan string sementara dalam kasus operator + , Anda juga dapat menggabungkan string dengan tipe data apa pun.

    Sebagai contoh:

    String s = 10 + "Hello";

    Dalam hal ini, outputnya harus 10Halo .

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

    Dalam kasus di atas, Anda harus memberikan dua string wajib.

  2. Perbedaan kedua dan utama antara + dan concat adalah:

    Kasus 1: Misalkan saya menggabungkan string yang sama dengan operator concat dengan cara ini

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

    Dalam hal ini jumlah objek yang dibuat dalam kumpulan adalah 7 seperti ini:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy

    Kasus 2:

    Sekarang saya akan menggabungkan string yang sama melalui operator +

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

    Dalam kasus di atas, jumlah objek yang dibuat hanya 5.

    Sebenarnya ketika kita meringkas string melalui + operator maka ia mempertahankan kelas StringBuffer untuk melakukan tugas yang sama sebagai berikut: -

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

    Dengan cara ini hanya akan membuat lima objek.

Jadi ini adalah perbedaan mendasar antara + dan metode concat . Nikmati :)

Deepak Sharma
sumber
Sayangku, Anda tahu betul bahwa string string mana pun diperlakukan sebagai objek String itu sendiri yang menyimpan dalam string pool. Jadi dalam hal ini kita memiliki 4 string literal. Jadi jelas setidaknya 4 objek harus dibuat di pool.
Deepak Sharma
1
Saya tidak berpikir begitu: String s="I"+"am"+"good"+"boy"; String s2 = "go".concat("od"); System.out.println(s2 == s2.intern());cetakan true, yang berarti "good"tidak ada di kolam string sebelum memanggilintern()
fabian
Saya hanya berbicara tentang baris ini String s = "I" + "am" + "good" + "boy"; Dalam hal ini semua 4 adalah string literal disimpan di kolam. Oleh karena itu 4 objek harus dibuat di kolam.
Deepak Sharma
4

Demi kelengkapan, saya ingin menambahkan bahwa definisi operator '+' dapat ditemukan di JLS SE8 15.18.1 :

Jika hanya satu ekspresi operan yang bertipe String, maka konversi string (§5.1.11) dilakukan pada operan lainnya untuk menghasilkan string pada saat run time.

Hasil penggabungan string adalah referensi ke objek String yang merupakan penggabungan dua string operan. Karakter operan kiri mendahului karakter operan kanan dalam string yang baru dibuat.

Objek String baru dibuat (§12.5) kecuali jika ekspresi adalah ekspresi konstan (§15.28).

Tentang implementasi JLS mengatakan hal berikut:

Implementasi dapat memilih untuk melakukan konversi dan penggabungan dalam satu langkah untuk menghindari membuat dan kemudian membuang objek String perantara. Untuk meningkatkan kinerja penggabungan string berulang, kompiler Java dapat menggunakan kelas StringBuffer atau teknik serupa untuk mengurangi jumlah objek String menengah yang dibuat dengan mengevaluasi ekspresi.

Untuk tipe primitif, implementasi juga dapat mengoptimalkan pembuatan objek wrapper dengan mengonversi langsung dari tipe primitif ke string.

Jadi menilai dari 'kompiler Java dapat menggunakan kelas StringBuffer atau teknik serupa untuk mengurangi', kompiler yang berbeda dapat menghasilkan byte-code yang berbeda.

dingalapadum
sumber
2

The + Operator dapat bekerja antara string dan string, char, integer, double atau mengambang nilai tipe data. Itu hanya mengkonversi nilai ke representasi string sebelum penggabungan.

The Operator concat hanya dapat dilakukan dan dengan string. Ia memeriksa kompatibilitas tipe data dan membuat kesalahan, jika tidak cocok.

Kecuali ini, kode yang Anda berikan melakukan hal yang sama.

Niyaz
sumber
2

Saya kira tidak.

a.concat(b)diimplementasikan dalam String dan saya pikir implementasi tidak banyak berubah sejak mesin java awal. The +pelaksanaan operasi tergantung pada versi Jawa dan compiler. Saat +ini diimplementasikan menggunakanStringBuffer untuk membuat operasi secepat mungkin. Mungkin di masa depan, ini akan berubah. Dalam versi sebelumnya dari +operasi java pada Strings jauh lebih lambat karena menghasilkan hasil antara.

Saya kira itu +=diimplementasikan menggunakan +dan dioptimalkan serupa.

Bartosz Bierkowski
sumber
7
"Saat ini + diimplementasikan menggunakan StringBuffer" False It's StringBuilder. StringBuffer adalah alat threadsafe dari StringBuilder.
Frederic Morin
1
Dulu StringBuffer sebelum java 1.5, karena itu adalah versi ketika StringBuilder pertama kali diperkenalkan.
ccpizza
0

Saat menggunakan +, kecepatan berkurang seiring bertambahnya panjang string, tetapi saat menggunakan concat, kecepatannya lebih stabil, dan opsi terbaik adalah menggunakan kelas StringBuilder yang memiliki kecepatan stabil untuk melakukan itu.

Saya kira Anda bisa mengerti mengapa. Tetapi cara terbaik untuk membuat string panjang adalah menggunakan StringBuilder () dan append (), kecepatan keduanya tidak dapat diterima.

iamreza
sumber
1
menggunakan operator + sama dengan menggunakan StringBuilder ( docs.oracle.com/javase/specs/jls/se8/html/… )
ihebiheb