Apa itu Java String interning?

234

Apa itu String Interning di Jawa, kapan saya harus menggunakannya, dan mengapa ?

saplingPro
sumber
2
if String a = new String("abc"); String b = new String("abc"); thena.intern() == b.intern()
Asanka Siriwardena
Contoh interning String Checkout: algs4.cs.princeton.edu/12oop/MutableString.java.html
Ronak Poriya
Apakah String.intern()tergantung pada ClassLoader, artinya, Apakah classloader yang berbeda menciptakan "berbeda" String, menyebabkan perbedaan intern?
AlikElzin-kilaka
1
@ AlikElzin-kilaka no, classloader sepenuhnya tidak relevan untuk pemagangan string. Lain kali Anda memiliki pertanyaan, harap buka pertanyaan baru alih-alih mempostingnya sebagai komentar untuk pertanyaan lain.
Holger

Jawaban:

233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

Pada dasarnya melakukan String.intern () pada serangkaian string akan memastikan bahwa semua string yang memiliki konten yang sama memiliki memori yang sama. Jadi jika Anda memiliki daftar nama tempat 'john' muncul 1000 kali, dengan magang Anda memastikan hanya satu 'john' yang benar-benar dialokasikan memori.

Ini dapat berguna untuk mengurangi kebutuhan memori program Anda. Namun perlu diingat bahwa cache dikelola oleh JVM di memori permanen yang biasanya terbatas dalam ukuran dibandingkan dengan tumpukan sehingga Anda tidak boleh menggunakan intern jika Anda tidak memiliki terlalu banyak nilai duplikat.


Lebih lanjut tentang kendala memori menggunakan intern ()

Di satu sisi, memang benar bahwa Anda dapat menghapus duplikat String dengan menginternalisasi mereka. Masalahnya adalah bahwa string yang diinternalisasi pergi ke Generasi Permanen, yang merupakan area JVM yang dicadangkan untuk objek non-pengguna, seperti Kelas, Metode dan objek JVM internal lainnya. Ukuran area ini terbatas, dan biasanya jauh lebih kecil dari heap. Memanggil intern () pada sebuah String memiliki efek memindahkannya dari tumpukan ke generasi permanen, dan Anda berisiko kehabisan ruang PermGen.

- Dari: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


Dari JDK 7 (maksud saya di HotSpot), ada sesuatu yang berubah.

Di JDK 7, string yang diinternir tidak lagi dialokasikan dalam generasi permanen heap Jawa, tetapi sebaliknya dialokasikan di bagian utama heap Jawa (dikenal sebagai generasi muda dan tua), bersama dengan objek lain yang dibuat oleh aplikasi . Perubahan ini akan menghasilkan lebih banyak data yang berada di heap Java utama, dan lebih sedikit data dalam generasi permanen, dan dengan demikian mungkin memerlukan ukuran tumpukan yang harus disesuaikan. Sebagian besar aplikasi hanya akan melihat perbedaan yang relatif kecil dalam penggunaan tumpukan karena perubahan ini, tetapi aplikasi yang lebih besar yang memuat banyak kelas atau menggunakan metode String.intern () akan melihat perbedaan yang lebih signifikan.

- Dari Java SE 7 Fitur dan Peningkatan

Pembaruan: String magang disimpan di tumpukan utama dari Java 7 dan seterusnya. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Ashwinee K Jha
sumber
1
"Tapi ketahuilah bahwa cache dikelola oleh JVM di memori permanen yang biasanya berukuran terbatas ......" Bisakah Anda menjelaskan ini? Saya tidak mengerti
saplingPro
2
string "yang diinternir" disimpan di wilayah memori khusus di JVM. Wilayah memori ini biasanya memiliki ukuran tetap, dan bukan bagian dari Java Heap biasa tempat data lain disimpan. Karena ukurannya yang tetap, mungkin saja wilayah memori permanen ini terisi dengan semua string Anda, yang mengarah ke masalah jelek (kelas tidak dapat dimuat dan hal-hal lain).
cello
@cello begitu, apakah ini mirip dengan caching?
saplingPro
8
@ grassPro: Ya, ini semacam caching, yang secara asli disediakan oleh JVM. Sebagai catatan, karena penggabungan Sun / Oracle JVM dan JRockit, para insinyur JVM mencoba untuk menyingkirkan wilayah memori permanen di JDK 8 ( openjdk.java.net/jeps/122 ), sehingga tidak akan ada batasan ukuran apa pun di masa depan.
cello
9
Pemrogram juga harus menyadari bahwa string interning dapat memiliki implikasi keamanan. Jika Anda memiliki teks sensitif seperti kata sandi sebagai string dalam memori, itu mungkin tinggal di memori untuk waktu yang sangat lama bahkan jika objek string yang sebenarnya telah lama GC. Itu bisa menyusahkan jika orang jahat entah bagaimana mendapatkan akses ke memori dump. Masalah ini ada bahkan tanpa magang (karena GC adalah non-deterministik untuk memulai dengan dll), tetapi membuatnya agak lebih buruk. Itu selalu merupakan ide yang baik untuk digunakan char[]alih-alih Stringuntuk teks sensitif dan nolkan segera setelah itu tidak lagi diperlukan.
chris
71

Ada beberapa pertanyaan "wawancara menarik", seperti mengapa Anda mendapatkan yang sama! jika Anda mengeksekusi potongan kode di bawah ini.

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

Jika Anda ingin membandingkan Strings yang harus Anda gunakan equals(). Di atas akan mencetak sama dengan karena testStringsudah diinternir untuk Anda oleh kompiler. Anda dapat menginternir string sendiri menggunakan metode intern seperti yang ditunjukkan dalam jawaban sebelumnya ....

maslan
sumber
5
Contoh Anda rumit karena akan menghasilkan cetakan yang sama bahkan jika Anda menggunakan equalsmetode ini. Anda mungkin ingin menambahkan new String()perbandingan untuk menunjukkan perbedaan lebih jelas.
giannis christofakis
@giannischristofakis tetapi jika kita menggunakan String baru (), bukankah == akan gagal? Apakah java secara otomatis menginternalisasi string yang baru juga?
Deepak Selvakumar
@giannischristofakis tentu saja jika Anda menggunakan String baru () itu akan gagal pada ==. tetapi String baru (...). intern () tidak akan gagal pada == karena intern akan mengembalikan string yang sama. Anggap saja kompiler sedang mengerjakan String baru (). Intern dalam literal
maslan
42

JLS

JLS 7 3.10.5 mendefinisikannya dan memberikan contoh praktis:

Selain itu, string literal selalu merujuk ke instance String kelas yang sama. Ini karena string literal - atau, lebih umum, string yang merupakan nilai ekspresi konstan (§15.28) - "diinternir" sehingga dapat berbagi contoh yang unik, menggunakan metode String.intern.

Contoh 3.10.5-1. String Literal

Program yang terdiri dari unit kompilasi (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

dan unit kompilasi:

package other;
public class Other { public static String hello = "Hello"; }

menghasilkan output:

true true true true false true

JVMS

JVMS 7 5.1 mengatakan bahwa interning diimplementasikan secara ajaib dan efisien dengan CONSTANT_String_infostruct khusus (tidak seperti kebanyakan objek lain yang memiliki representasi lebih umum):

Literal string adalah referensi ke turunan dari String kelas, dan diturunkan dari struktur CONSTANT_String_info (§4.4.3) dalam representasi biner dari kelas atau antarmuka. Struktur CONSTANT_String_info memberikan urutan titik kode Unicode yang membentuk string literal.

Bahasa pemrograman Java mensyaratkan bahwa literal string identik (yaitu, literal yang berisi urutan titik kode yang sama) harus merujuk ke instance String kelas yang sama (JLS §3.10.5). Selain itu, jika metode String.intern dipanggil pada sembarang string, hasilnya adalah referensi ke instance kelas yang sama yang akan dikembalikan jika string itu muncul sebagai literal. Dengan demikian, ekspresi berikut harus memiliki nilai true:

("a" + "b" + "c").intern() == "abc"

Untuk mendapatkan string literal, Java Virtual Machine memeriksa urutan poin kode yang diberikan oleh struktur CONSTANT_String_info.

  • Jika metode String.intern sebelumnya telah dipanggil pada turunan String kelas yang berisi urutan titik kode Unicode yang identik dengan yang diberikan oleh struktur CONSTANT_String_info, maka hasil derivasi string literal adalah referensi ke turunan String String yang sama.

  • Jika tidak, instance baru String kelas dibuat berisi urutan titik kode Unicode yang diberikan oleh struktur CONSTANT_String_info; referensi ke instance kelas adalah hasil derivasi string literal. Akhirnya, metode intern instance String baru dipanggil.

Bytecode

Mari kita mendekompilasi beberapa bytecode OpenJDK 7 untuk melihat tindakan interning.

Jika kami mendekompilasi:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

yang kita miliki di kolam konstan:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

dan main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

Perhatikan caranya:

  • 0dan 3: ldc #2konstanta yang sama dimuat (literal)
  • 12: contoh string baru dibuat (dengan #2 sebagai argumen)
  • 35: adan cdibandingkan sebagai objek biasa denganif_acmpne

Representasi string konstan cukup ajaib pada bytecode:

dan kutipan JVMS di atas tampaknya mengatakan bahwa setiap kali Utf8 menunjuk adalah sama, maka instance identik dimuat oleh ldc.

Saya telah melakukan tes serupa untuk bidang, dan:

  • static final String s = "abc"menunjuk ke tabel konstan melalui Atribut ConstantValue
  • bidang non-final tidak memiliki atribut itu, tetapi masih dapat diinisialisasi dengan ldc

Kesimpulan : ada dukungan bytecode langsung untuk kumpulan string, dan representasi memori efisien.

Bonus: bandingkan dengan kelompok Integer , yang tidak memiliki dukungan bytecode langsung (yaitu tidak ada CONSTANT_String_infoanalog).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
19

Pembaruan untuk Java 8 atau plus . Di Java 8, ruang PermGen (Generasi Permanen) dihapus dan diganti oleh Meta Space. Memori kumpulan string dipindahkan ke tumpukan JVM.

Dibandingkan dengan Java 7, ukuran string pool bertambah di heap. Oleh karena itu, Anda memiliki lebih banyak ruang untuk Strings yang terinternalisasi, tetapi Anda memiliki lebih sedikit memori untuk seluruh aplikasi.

Satu hal lagi, Anda sudah tahu bahwa ketika membandingkan 2 (rujukan) objek di Jawa, ' ==' digunakan untuk membandingkan referensi objek, ' equals' digunakan untuk membandingkan konten objek.

Mari kita periksa kode ini:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

Hasil:

value1 == value2 ---> benar

value1 == value3 ---> salah

value1.equals(value3) ---> benar

value1 == value3.intern() ---> benar

Itu sebabnya Anda harus menggunakan ' equals' untuk membandingkan 2 objek String. Dan itulah cara intern()berguna.

nguyentt
sumber
2

String interning adalah teknik optimisasi oleh kompiler. Jika Anda memiliki dua literal string identik dalam satu unit kompilasi maka kode yang dihasilkan memastikan bahwa hanya ada satu objek string yang dibuat untuk semua instance dari literal itu (karakter terlampir dalam tanda kutip ganda) di dalam rakitan.

Saya dari latar belakang C #, jadi saya bisa menjelaskan dengan memberikan contoh dari itu:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

output dari perbandingan berikut:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Catatan1 : Objek dibandingkan dengan referensi.

Note2 : typeof (int). Nama dievaluasi dengan metode refleksi sehingga tidak dievaluasi pada waktu kompilasi. Di sini perbandingan ini dibuat pada waktu kompilasi.

Analisis Hasil: 1) benar karena keduanya mengandung literal yang sama dan kode yang dihasilkan hanya akan memiliki satu objek referensi "Int32". Lihat Catatan 1 .

2) benar karena isi dari kedua nilai diperiksa yang sama.

3) FALSE karena str2 dan obj tidak memiliki literal yang sama. Lihat Catatan 2 .

Robin Gupta
sumber
3
Itu lebih kuat dari itu. Literal String apa pun yang dimuat oleh classloader yang sama akan merujuk ke String yang sama. Lihat Spesifikasi JLS dan JVM.
Marquis of Lorne
1
@ user207421 sebenarnya, bahkan tidak relevan dengan classloader yang dimiliki string literal.
Holger
1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.
Rohan Kshirsagar
sumber
0

Dari buku Deshmukh Programmer OCP Java SE 11 saya menemukan penjelasan termudah untuk Interning yang mengikuti sebagai berikut: Karena string adalah objek dan karena semua objek di Jawa selalu disimpan hanya dalam ruang heap, semua string disimpan dalam ruang heap. Namun, Java menyimpan string yang dibuat tanpa menggunakan kata kunci baru di area khusus ruang heap, yang disebut "string pool". Java menyimpan string yang dibuat menggunakan kata kunci baru di ruang heap biasa.

Tujuan dari kumpulan string adalah untuk mempertahankan serangkaian string unik. Setiap kali Anda membuat string baru tanpa menggunakan kata kunci baru, Java memeriksa apakah string yang sama sudah ada di kumpulan string. Jika ya, Java mengembalikan referensi ke objek String yang sama dan jika tidak, Java membuat objek String baru di kumpulan string dan mengembalikan referensi. Jadi, misalnya, jika Anda menggunakan string "halo" dua kali dalam kode Anda seperti yang ditunjukkan di bawah ini, Anda akan mendapatkan referensi ke string yang sama. Kami benar-benar dapat menguji teori ini dengan membandingkan dua variabel referensi yang berbeda menggunakan operator == seperti yang ditunjukkan dalam kode berikut:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

== operator hanya memeriksa apakah dua referensi menunjuk ke objek yang sama atau tidak dan mengembalikan true jika mereka. Dalam kode di atas, str2 mendapatkan referensi ke objek String yang sama yang telah dibuat sebelumnya. Namun, str3 dan STR4 mendapatkan referensi ke dua objek String yang sama sekali berbeda. Itulah sebabnya str1 == str2 pengembalian benar tetapi str1 == str3 dan str3 == STR4 kembali palsu. Bahkan, ketika Anda melakukan String baru ("halo"); dua objek String dibuat bukan hanya satu jika ini adalah pertama kalinya string "halo" digunakan di mana saja dalam program - satu di kumpulan string karena penggunaan string yang dikutip, dan satu di ruang tumpukan reguler karena penggunaan kata kunci baru.

Penyatuan string adalah cara Java menyimpan memori program dengan menghindari pembuatan beberapa objek String yang berisi nilai yang sama. Dimungkinkan untuk mendapatkan string dari kumpulan string untuk string yang dibuat menggunakan kata kunci baru dengan menggunakan metode intern String. Ini disebut "magang" objek string. Sebagai contoh,

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
Hamza
sumber