Java String interning nedir?


234

Java'da String Interning nedir , ne zaman kullanmalıyım ve neden ?



2
eğer String a = new String("abc"); String b = new String("abc"); öyleysea.intern() == b.intern()
Asanka Siriwardena

Checkout String interning
Ronak

Farklı sınıf yükleyicinin "farklı" lar yaratması, farklı s'lara neden String.intern()olması ClassLoader, buna bağlı mı ? Stringintern
AlikElzin-kilaka

1
@ AlikElzin-kilaka hayır, sınıf yükleyicileri telin interningi için tamamen önemsizdir. Bir daha sorunuz olduğunda, lütfen farklı bir soruya yorum olarak göndermek yerine yeni bir soru açın .
Holger

Yanıtlar:


233

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

Temelde bir dizi dizede String.intern () yapmak, aynı içeriğe sahip tüm dizelerin aynı belleği paylaşmasını sağlayacaktır. Yani 'john'un 1000 kez göründüğü isimler listeniz varsa, staj yaparak sadece bir' john 'un gerçekten bellek tahsis edildiğinden emin olursunuz.

Bu, programınızın bellek gereksinimlerini azaltmak için yararlı olabilir. Ancak önbelleğin JVM tarafından genellikle yığınla karşılaştırıldığında boyutu sınırlı olan kalıcı bellek havuzunda tutulduğunu unutmayın, bu nedenle çok fazla yinelenen değeriniz yoksa stajyer kullanmamanız gerekir.


Stajyer () kullanmanın bellek kısıtlamaları hakkında daha fazla bilgi

Bir yandan, String kopyalarını içselleştirerek kaldırabileceğiniz doğrudur. Sorun içselleştirilmiş dizelerin JVM'nin Sınıflar, Yöntemler ve diğer dahili JVM nesneleri gibi kullanıcı olmayan nesneler için ayrılmış bir alanı olan Kalıcı Nesil'e gitmesidir. Bu alanın boyutu sınırlıdır ve genellikle yığından çok daha küçüktür. Bir String üzerinde intern () çağrılması, onu yığından kalıcı nesle taşıma etkisine sahiptir ve PermGen alanınızın bitmesi riski vardır.

- Gönderen: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


JDK 7'den (yani HotSpot'ta) bir şey değişti.

JDK 7'de, sabit dizeler artık Java yığınının kalıcı neslinde değil, uygulama tarafından oluşturulan diğer nesnelerle birlikte Java yığınının ana bölümünde (genç ve eski kuşaklar olarak bilinir) ayrılır. . Bu değişiklik, ana Java yığınında daha fazla veriye ve kalıcı oluşturmada daha az veriye neden olur ve bu nedenle yığın boyutlarının ayarlanmasını gerektirebilir. Çoğu uygulama, bu değişiklik nedeniyle yığın kullanımında yalnızca nispeten küçük farklılıklar görür, ancak birçok sınıf yükleyen veya String.intern () yöntemini yoğun kullanan daha büyük uygulamalar daha önemli farklılıklar görür.

- Java SE 7 Özellikleri ve Geliştirmeleri'nden

Güncelleme: Staj dizeleri Java 7'den itibaren ana yığın halinde saklanır. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes


1
"Ama önbellek JVM tarafından genellikle sınırlı boyutta olan kalıcı bellek havuzunda tutulduğunu unutmayın ......" Bunu açıklayabilir misiniz? Anlamadım
SaplingPro

2
"interned" dizeleri JVM'deki özel bir bellek bölgesinde saklanır. Bu bellek bölgesi tipik olarak sabit bir boyuta sahiptir ve diğer verilerin depolandığı normal Java Yığınının bir parçası değildir. Sabit boyut nedeniyle, bu kalıcı bellek bölgesinin tüm dizelerinizle doldurulması çirkin sorunlara yol açabilir (sınıflar yüklenemez ve diğer şeyler).
viyolonsel

@cello öyleyse, önbelleğe almaya benzer mi?
saplingPro

8
@grassPro: Evet, JVM tarafından yerel olarak sağlanan bir tür önbellekleme. Not olarak, Sun / Oracle JVM ve JRockit'in birleşmesi nedeniyle, JVM mühendisleri JDK 8'deki ( openjdk.java.net/jeps/122 ) kalıcı bellek bölgesinden kurtulmaya çalışıyor , bu yüzden olmayacak gelecekte herhangi bir boyut sınırlaması.
viyolonsel

9
Programcılar ayrıca string interning'in güvenlikle ilgili sonuçları olabileceğinin farkında olmalıdır. Bellekte dizeler gibi şifreler gibi hassas metinleriniz varsa, gerçek dize nesneleri uzun süre GC olmuş olsa bile bellekte çok uzun süre kalabilir. Kötü adamlar bir şekilde bellek dökülmesine erişirse bu sorun yaratabilir. Bu sorun, stajyer olmadan da mevcuttur (GC, vb. İle başlamak için deterministik olmadığından), ancak biraz daha kötü hale getirir. Hassas metin char[]yerine kullanmak her zaman iyi bir fikirdir Stringve artık gerekli olmadığı anda sıfırlamak iyi olur.
chris

71

Niçin eşit olduğunuz gibi bazı "akılda kalıcı röportaj" soruları var ! Aşağıdaki kod parçasını yürütürseniz.

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

Dizeleri karşılaştırmak istiyorsanız kullanmalısınız equals(). Yukarıdakiler eşit olacaktır çünkü derleyici sizin için testStringzaten stajyerdir . Önceki cevaplarda gösterildiği gibi stajyer yöntemini kullanarak dizeleri kendiniz stajyer yapabilirsiniz ....


5
Örneğiniz zor çünkü equalsyöntemi kullansanız bile aynı baskıya neden olacak . new String()Ayrımı daha net göstermek için bir karşılaştırma eklemek isteyebilirsiniz .
giannis christofakis

@giannischristofakis ama yeni String () kullanırsak == başarısız olmaz mı? Java, yeni dizeleri de otomatik olarak içselleştiriyor mu?
Deepak Selvakumar

@giannischristofakis Elbette yeni String () kullanırsanız == başarısız olur. ancak yeni String (...). intern () == üzerinde başarısız olmaz çünkü stajyer aynı dizeyi döndürür. Basit varsayım derleyici
literals

42

JLS

JLS 7 3.10.5 bunu tanımlar ve pratik bir örnek verir:

Ayrıca, bir dizgi değişmezi her zaman String sınıfının aynı örneğini belirtir. Bunun nedeni, dize değişmezlerinin - veya daha genel olarak, sabit ifadelerin (§15.28) değerleri olan dizelerin - String.intern yöntemini kullanarak benzersiz örnekleri paylaşmak için "interned" olmasıdır.

Örnek 3.10.5-1. Dize Değişmezleri

Derleme biriminden oluşan program (§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"; }

ve derleme birimi:

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

çıktı üretir:

true true true true false true

JVM'lerle

JVMS 7 5.1, stajın özel bir CONSTANT_String_infoyapı ile sihirli ve verimli bir şekilde uygulandığını söylüyor (daha genel temsilleri olan diğer nesnelerin aksine):

Dize değişmezi, String sınıfının bir örneğine başvurudır ve bir sınıfın veya arabirimin ikili gösterimindeki CONSTANT_String_info yapısından (§4.4.3) türetilir. CONSTANT_String_info yapısı, dizgi değişmezini oluşturan Unicode kod noktalarının sırasını verir.

Java programlama dili, aynı dize değişmezlerinin (yani, aynı kod noktaları sırasını içeren değişmez değerlerin) aynı Dize sınıfı örneğine (JLS §3.10.5) başvurmasını gerektirir. Ayrıca, String.intern yöntemi herhangi bir dizede çağrılırsa, sonuç, bu dize değişmez olarak görünürse döndürülecek aynı sınıf örneğine başvurudur. Bu nedenle, aşağıdaki ifade true değerine sahip olmalıdır:

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

Bir dizgi değişmezi türetmek için Java Sanal Makinesi, CONSTANT_String_info yapısı tarafından verilen kod noktalarının sırasını inceler.

  • String.intern yöntemi daha önce CONSTANT_String_info yapısı tarafından verilenle aynı Unicode kod noktaları dizisini içeren bir sınıf String örneğinde çağrılmışsa, dizgi değişmezinin türetilmesinin sonucu, sınıf String'in aynı örneğine bir başvurudur.

  • Aksi takdirde, CONSTANT_String_info yapısı tarafından verilen Unicode kod noktalarının sırasını içeren yeni bir String sınıfı örneği oluşturulur; bu sınıf örneğine başvuru, dizgi değişmezinin türetilmesinin sonucudur. Son olarak, yeni String örneğinin stajyer yöntemi çağrılır.

Bytecode

Stajyerin nasıl çalıştığını görmek için bazı OpenJDK 7 bayt kodunu kodalım.

Biz kodalar:

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

sürekli havuzumuz var:

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

ve 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

Nasıl yapıldığına dikkat edin:

  • 0ve 3: aynı ldc #2sabit yüklenir (değişmez değerler)
  • 12: yeni bir dize örneği oluşturulur ( #2argüman olarak)
  • 35: ave cnormal nesnelerle karşılaştırılır.if_acmpne

Sabit dizelerin temsili bayt kodunda oldukça büyülüdür:

ve yukarıdaki JVMS alıntısı, Utf8'in işaret ettiği her zaman aynı olduğunda, aynı örneklerin yüklendiğini söylüyor ldc.

Alanlar için benzer testler yaptım ve:

  • static final String s = "abc"ConstantValue Özelliği aracılığıyla sabit tabloyu gösterir
  • nihai olmayan alanlar bu özelliğe sahip değildir, ancak yine de ile başlatılabilir ldc

Sonuç : dize havuzu için doğrudan bayt kodu desteği vardır ve bellek gösterimi etkilidir.

Bonus: bunu doğrudan bayt kodu desteği olmayan (yani analog olmayan ) Tamsayı havuzuyla karşılaştırın CONSTANT_String_info.


19

Java 8 veya üzeri için güncelleme . Java 8'de PermGen (Kalıcı Nesil) alanı kaldırılır ve yerine Meta Uzay kullanılır. Dize havuzu belleği JVM yığınına taşınır.

Java 7 ile karşılaştırıldığında, Dize havuzu boyutu yığın halinde artar. Bu nedenle, dahili Dizeler için daha fazla alanınız vardır, ancak tüm uygulama için daha az belleğiniz vardır.

Bir şey daha var, Java'daki 2 (nesnenin referansı) nesnelerini ==karşılaştırırken, ' equals' nesnenin referansını karşılaştırmak için, ' ' nesnenin içeriğini karşılaştırmak için kullanıldığını zaten biliyordunuz .

Bu kodu kontrol edelim:

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

Sonuç:

value1 == value2 ---> doğru

value1 == value3 ---> yanlış

value1.equals(value3) ---> doğru

value1 == value3.intern() ---> doğru

Bu nedenle ' equals' 2 String nesnesini karşılaştırmak için kullanmalısınız . Ve işte intern()böyle faydalıdır.


2

String interning, derleyici tarafından bir optimizasyon tekniğidir. Bir derleme biriminde iki özdeş dize değişmezine sahipseniz, oluşturulan kod, derlemede bu değişmezin tüm örneği (çift tırnak içine alınmış karakterler) için yalnızca bir dize nesnesinin oluşturulmasını sağlar.

Ben C # arka plan, bu yüzden bir örnek vererek açıklayabilirsiniz:

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

Aşağıdaki karşılaştırmaların çıktısı:

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

Not1 : Nesneler referans olarak karşılaştırılmıştır.

Not2 : typeof (int) .Ad yansıma yöntemi ile değerlendirilir, böylece derleme zamanında değerlendirilmez. Burada bu karşılaştırmalar derleme zamanında yapılır.

Sonuçların Analizi: 1) true, çünkü her ikisi de aynı değişmezi içerdiğinden ve üretilen kodun "Int32" ye referans veren tek bir nesnesi olacaktır. Not 1'e bakınız .

2) true, çünkü her iki değerin içeriği de kontrol edilir.

3) YANLIŞ çünkü str2 ve obj aynı değişmez değere sahip değildir. Not 2'ye bakınız .


3
Bundan daha güçlü. Aynı classloader tarafından yüklenen herhangi bir String değişmezi aynı String'i belirtir. Bkz. JLS ve JVM Spesifikasyonu.
Lorne Marquis

1
@ user207421 Aslında, dize değişmezinin hangi sınıf yükleyiciye ait olduğu bile ilgisizdir.
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.

0

OCP Java SE 11 Programcı Deshmukh kitabından, aşağıdaki gibi giden Interning için en kolay açıklamayı buldum: Dizeler nesneler olduğundan ve Java'daki tüm nesneler her zaman yalnızca yığın alanında depolandığından, tüm dizeler yığın alanında depolanır. Bununla birlikte, Java, yeni anahtar sözcüğü kullanılmadan oluşturulan dizeleri "dize havuzu" adı verilen özel bir alanda tutar. Java, yeni anahtar kelime kullanılarak oluşturulan dizeleri normal yığın alanında tutar.

Dize havuzunun amacı, bir dizi benzersiz dize korumaktır. Yeni anahtar kelimeyi kullanmadan yeni bir dize oluşturduğunuzda, Java dize havuzunda aynı dizenin var olup olmadığını denetler. Varsa, Java aynı String nesnesine bir başvuru döndürür ve bu olmazsa Java, dize havuzunda yeni bir String nesnesi oluşturur ve başvurusunu döndürür. Örneğin, aşağıda gösterildiği gibi kodunuzda iki kez "merhaba" dizesini kullanırsanız, aynı dizeye bir başvuru alırsınız. Aşağıdaki kodda gösterildiği gibi == operatörünü kullanarak iki farklı referans değişkeni karşılaştırarak bu teoriyi test edebiliriz :

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 

== işleci, iki başvurunun aynı nesneyi gösterip göstermediğini denetler ve varsa true değerini döndürür. Yukarıdaki kodda, str2 , daha önce oluşturulan aynı String nesnesine başvuruyu alır. Ancak str3 ve str4 olsun tamamen farklı iki dize nesnelere referanslar. İşte bu yüzden dizge1 == str2 döner doğru ama dizge1 == str3 ve str3 == str4 dönüş false. Aslında, yeni String ("merhaba") yaptığınızda; programın herhangi bir yerinde "merhaba" dizesi ilk kez kullanılıyorsa, tek bir yerine iki String nesnesi oluşturulur. yeni anahtar kelime kullanımı.

String pooling, Java'nın aynı değeri içeren birden fazla String nesnesi oluşturmaktan kaçınarak program belleğini kaydetme yoludur. String intern yöntemini kullanarak yeni anahtar sözcük kullanılarak oluşturulan bir dize için dize havuzundan bir dize almak mümkündür. Buna string nesnelerinin "interning" denir. Örneğin,

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
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.