Co to jest interning Java String?

234

Co to jest String Interning w Javie, kiedy powinienem go używać i dlaczego ?

saplingPro
źródło
2
jeśli String a = new String("abc"); String b = new String("abc"); toa.intern() == b.intern()
Asanka Siriwardena
Kasa Przykład internowania ciągów: algs4.cs.princeton.edu/12oop/MutableString.java.html
Ronak Poriya
Czy String.intern()zależy to od tego ClassLoader, czy różne moduły ładujące klasy tworzą „różne” String, powodując różne intern?
AlikElzin-kilaka
1
@ AlikElzin-kilaka nie, moduły ładujące klasy są całkowicie nieistotne dla internalizacji łańcucha. Następnym razem, gdy masz pytanie, otwórz nowe pytanie zamiast zamieszczać je jako komentarz do innego pytania.
Holger

Odpowiedzi:

233

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

Zasadniczo wykonanie String.intern () na szeregu ciągów zapewni, że wszystkie ciągi mające tę samą zawartość będą miały tę samą pamięć. Więc jeśli masz listę nazwisk, w których „John” pojawia się 1000 razy, internując masz pewność, że tylko jeden „John” jest rzeczywiście przydzielony do pamięci.

Może to być przydatne w celu zmniejszenia wymagań dotyczących pamięci twojego programu. Ale pamiętaj, że pamięć podręczna jest utrzymywana przez JVM w stałej puli pamięci, która zwykle ma ograniczony rozmiar w porównaniu do sterty, więc nie powinieneś używać intern, jeśli nie masz zbyt wielu zduplikowanych wartości.


Więcej informacji na temat ograniczeń pamięci związanych z używaniem intern ()

Z jednej strony prawdą jest, że można usunąć duplikaty ciągów poprzez ich internalizację. Problem polega na tym, że zinternalizowane ciągi idą do stałej generacji, która jest obszarem JVM zarezerwowanym dla obiektów innych niż użytkownik, takich jak klasy, metody i inne wewnętrzne obiekty JVM. Rozmiar tego obszaru jest ograniczony i zwykle jest znacznie mniejszy niż hałda. Wywołanie intern () na łańcuchu powoduje przeniesienie go ze sterty do stałego generowania i ryzykujesz wyczerpanie miejsca PermGen.

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


Od JDK 7 (mam na myśli w HotSpot) coś się zmieniło.

W JDK 7 internowane ciągi nie są już alokowane w stałej generacji sterty Java, ale zamiast tego są alokowane w głównej części sterty Java (zwanej młodą i starą generacją), wraz z innymi obiektami tworzonymi przez aplikację . Ta zmiana spowoduje, że więcej danych będzie znajdować się w głównej sterty Java, a mniej danych w generacji stałej, a zatem może wymagać dostosowania wielkości sterty. Większość aplikacji zobaczy tylko stosunkowo niewielkie różnice w stosie ze względu na tę zmianę, ale większe aplikacje, które ładują wiele klas lub intensywnie korzystają z metody String.intern (), zobaczą bardziej znaczące różnice.

- Z funkcji i ulepszeń Java SE 7

Aktualizacja: Wewnętrzne ciągi są przechowywane w stercie głównym od Java 7 i późniejszych. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Ashwinee K Jha
źródło
1
„Należy jednak pamiętać, że JVM utrzymuje pamięć podręczną w stałej puli pamięci, która zwykle ma ograniczony rozmiar ......” Czy możesz to wyjaśnić? Nie zrozumiałem
saplingPro
2
„internowane” ciągi są przechowywane w specjalnym regionie pamięci w JVM. Ten region pamięci ma zazwyczaj stały rozmiar i nie jest częścią zwykłego stosu Java, w którym przechowywane są inne dane. Ze względu na ustalony rozmiar może się zdarzyć, że ten stały obszar pamięci zostanie zapełniony wszystkimi ciągami, co spowoduje brzydkie problemy (klas nie można załadować i innych rzeczy).
wiolonczela
@cello więc czy to jest podobne do buforowania?
saplingPro
8
@grassPro: Tak, jest to rodzaj buforowania, który jest natywnie zapewniany przez JVM. Uwaga: ze względu na połączenie Sun / Oracle JVM i JRockit, inżynierowie JVM próbują pozbyć się stałego regionu pamięci w JDK 8 ( openjdk.java.net/jeps/122 ), więc nie będzie wszelkie ograniczenia wielkości w przyszłości.
wiolonczela
9
Programiści powinni również zdawać sobie sprawę, że internowanie ciągów może mieć wpływ na bezpieczeństwo. Jeśli masz w pamięci wrażliwy tekst, taki jak hasła jako ciągi znaków, może on pozostać w pamięci przez bardzo długi czas, nawet jeśli rzeczywiste obiekty ciągów znaków były długo używane w GC. Może to być kłopotliwe, jeśli złoczyńcy w jakiś sposób uzyskają dostęp do zrzutu pamięci. Ten problem istnieje nawet bez internowania (ponieważ GC nie jest deterministyczny na początek itp.), Ale czyni go nieco gorszym. Zawsze warto używać char[]zamiast Stringwrażliwego tekstu i wyzerować go, gdy tylko nie będzie już potrzebny.
Chris
71

Są pewne „chwytliwe wywiady”, na przykład dlaczego jesteś równy! jeśli wykonasz poniższy fragment kodu.

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

Jeśli chcesz porównać ciągi, których powinieneś użyć equals(). Powyższe spowoduje wydrukowanie równe, ponieważ testStringjest już internowane przez kompilator. Możesz internować łańcuchy samodzielnie za pomocą metody intern, jak pokazano w poprzednich odpowiedziach ....

maslan
źródło
5
Twój przykład jest trudny, ponieważ spowoduje to ten sam wydruk, nawet jeśli użyjesz tej equalsmetody. Możesz dodać new String()porównanie, aby wyraźniej pokazać rozróżnienie.
giannis christofakis
@giannischristofakis, ale jeśli użyjemy nowej String (), czy nie = = nie powiedzie się? Czy java automatycznie internalizuje również nowe ciągi?
Deepak Selvakumar
@giannischristofakis oczywiście, jeśli użyjesz nowego String (), to się nie powiedzie ==. ale nowy ciąg (...). intern () nie zawiedzie ==, ponieważ intern zwróci ten sam ciąg. Proste założenie, że kompilator robi nowy String (). Stażysta w literałach
maslan
42

JLS

JLS 7 3.10.5 definiuje to i podaje praktyczny przykład:

Co więcej, literał łańcuchowy zawsze odnosi się do tego samego wystąpienia klasy String. Wynika to z tego, że literały łańcuchowe - lub, bardziej ogólnie, łańcuchy, które są wartościami wyrażeń stałych (§ 15.28) - są „internowane”, aby dzielić unikalne instancje, przy użyciu metody String.intern.

Przykład 3.10.5-1. Literały smyczkowe

Program składający się z jednostki kompilacyjnej (§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"; }

oraz jednostka kompilacyjna:

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

daje wynik:

true true true true false true

JVMS

JVMS 7 5.1 mówi , że internowanie jest realizowane magicznie i skutecznie za pomocą dedykowanej CONSTANT_String_infostruktury (w przeciwieństwie do większości innych obiektów, które mają bardziej ogólne reprezentacje):

Literał łańcuchowy jest odwołaniem do instancji klasy Łańcuch i pochodzi ze struktury CONSTANT_String_info (§4.4.3) w binarnej reprezentacji klasy lub interfejsu. Struktura CONSTANT_String_info podaje sekwencję punktów kodowych Unicode stanowiących literał ciągu.

Język programowania Java wymaga, aby identyczne literały łańcuchowe (to znaczy literały zawierające tę samą sekwencję punktów kodowych) musiały odnosić się do tej samej instancji klasy String (JLS §3.10.5). Ponadto, jeśli metoda String.intern zostanie wywołana na dowolnym ciągu, wynikiem jest odwołanie do tej samej instancji klasy, która zostałaby zwrócona, gdyby ten ciąg pojawił się jako literał. Dlatego poniższe wyrażenie musi mieć wartość true:

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

Aby uzyskać literał ciąg, wirtualna maszyna Java sprawdza sekwencję punktów kodu podaną przez strukturę CONSTANT_String_info.

  • Jeśli metoda String.intern została wcześniej wywołana na instancji klasy String zawierającej sekwencję punktów kodu Unicode identycznych z podanymi przez strukturę CONSTANT_String_info, to wynik wyprowadzenia literału łańcucha jest odniesieniem do tej samej instancji klasy String.

  • W przeciwnym razie tworzona jest nowa instancja klasy String zawierająca sekwencję punktów kodu Unicode podaną przez strukturę CONSTANT_String_info; odwołanie do tej instancji klasy jest wynikiem pochodnej literału łańcuchowego. Na koniec wywoływana jest metoda intern nowej instancji String.

Kod bajtowy

Dekompilujmy kod bajtowy OpenJDK 7, aby zobaczyć internowanie w akcji.

Jeśli dekompilujemy:

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

mamy na stałej puli:

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

i 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

Uwaga jak:

  • 0i 3: ldc #2ładowana jest ta sama stała (literały)
  • 12: tworzona jest nowa instancja ciągu (z #2argumentem jako)
  • 35: ai csą porównywane jako zwykłe obiekty zif_acmpne

Reprezentacja ciągów ciągłych jest dość magiczna w kodzie bajtowym:

a powyższy cytat JVMS wydaje się mówić, że ilekroć wskazany Utf8 jest taki sam, ładowane są identyczne instancje ldc.

Zrobiłem podobne testy dla pól i:

  • static final String s = "abc"wskazuje na stałą tabelę poprzez atrybut ConstantValue
  • pola nie-końcowe nie mają tego atrybutu, ale nadal można je zainicjować za pomocą ldc

Wniosek : istnieje bezpośrednia obsługa kodów bajtów dla puli ciągów, a reprezentacja pamięci jest wydajna.

Bonus: porównaj to z pulą liczb całkowitych , która nie ma bezpośredniego wsparcia dla kodu bajtowego (tzn. Nie ma CONSTANT_String_infoanalogu).

Ciro Santilli
źródło
19

Aktualizacja dla Java 8 lub nowszej . W Javie 8 przestrzeń PermGen (Permanent Generation) jest usuwana i zastępowana przez Meta Space. Pamięć puli ciągów jest przenoszona na stertę maszyny JVM.

W porównaniu z Javą 7 wielkość puli ciągów jest zwiększana w stercie. Dlatego masz więcej miejsca na zinternalizowane ciągi, ale masz mniej pamięci dla całej aplikacji.

Jeszcze jedna rzecz, wiesz już, że podczas porównywania 2 ( ==referencji ) obiektów w Javie „ ” służy do porównywania referencji do obiektu, „ equals” służy do porównywania zawartości obiektu.

Sprawdźmy ten kod:

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

Wynik:

value1 == value2 ---> prawda

value1 == value3 ---> false

value1.equals(value3) ---> prawda

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

Dlatego powinieneś użyć „ equals” do porównania 2 obiektów String. I to intern()jest przydatne.

nguyentt
źródło
2

Internowanie ciągów jest techniką optymalizacji przez kompilator. Jeśli masz dwie identyczne literały łańcuchowe w jednej jednostce kompilacji, wówczas wygenerowany kod zapewnia, że ​​istnieje tylko jeden obiekt łańcuchowy utworzony dla wszystkich wystąpień tego literału (znaki ujęte w podwójne cudzysłowy) w zestawie.

Jestem w tle C #, więc mogę wyjaśnić, podając przykład z tego:

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

wynik następujących porównań:

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

Uwaga 1 : Obiekty są porównywane przez odniesienie.

Uwaga 2 : typeof (int) .Nazwa jest obliczana metodą refleksyjną, więc nie jest obliczana podczas kompilacji. Tutaj porównuje się je w czasie kompilacji.

Analiza wyników: 1) prawda, ponieważ oba zawierają ten sam literał, a więc wygenerowany kod będzie miał tylko jeden obiekt odwołujący się do „Int32”. Uwaga 1 .

2) prawda, ponieważ sprawdzana jest zawartość obu wartości, która jest taka sama.

3) FAŁSZ, ponieważ str2 i obj nie mają tego samego literału. Patrz uwaga 2 .

Robin Gupta
źródło
3
Jest silniejszy niż to. Dowolny ciąg znaków załadowany przez ten sam moduł ładujący będzie odnosił się do tego samego ciągu. Zobacz specyfikację JLS i JVM.
Markiz Lorne
1
@ user207421 w rzeczywistości nie ma nawet znaczenia, do którego modułu ładującego należy łańcuch literału.
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
źródło
0

Z książki Desmukha programisty OCP Java SE 11 znalazłem najłatwiejsze wyjaśnienie dotyczące internowania, które brzmiało następująco: Ponieważ ciągi są obiektami, a ponieważ wszystkie obiekty w Javie są zawsze przechowywane tylko w przestrzeni sterty, wszystkie ciągi są przechowywane w przestrzeni sterty. Jednak Java utrzymuje ciągi utworzone bez użycia nowego słowa kluczowego w specjalnym obszarze przestrzeni sterty, który nazywa się „pulą ciągów”. Java utrzymuje ciągi utworzone przy użyciu nowego słowa kluczowego w zwykłej przestrzeni sterty.

Celem puli ciągów jest utrzymanie zestawu unikatowych ciągów. Za każdym razem, gdy tworzysz nowy ciąg bez użycia nowego słowa kluczowego, Java sprawdza, czy ten sam ciąg już istnieje w puli ciągów. Jeśli tak, Java zwraca odwołanie do tego samego obiektu String, a jeśli nie, Java tworzy nowy obiekt String w puli ciągów i zwraca swoje odwołanie. Na przykład, jeśli użyjesz w kodzie dwa razy łańcucha „cześć”, jak pokazano poniżej, otrzymasz odwołanie do tego samego ciągu. Możemy faktycznie przetestować tę teorię, porównując dwie różne zmienne odniesienia za pomocą operatora == , jak pokazano w następującym kodzie:

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 sprawdza po prostu, czy dwa odniesienia wskazują na ten sam obiekt, czy nie, i zwraca true, jeśli tak jest. W powyższym kodzie str2 pobiera odwołanie do tego samego obiektu String, który został wcześniej utworzony. Jednak str3 i str4 otrzymują odniesienia do dwóch całkowicie różnych obiektów String. Dlatego str1 == STR2 powraca prawda, ale str1 == str3 i str3 == STR4 return false. W rzeczywistości, kiedy robisz nowy ciąg („hello”); dwa obiekty String są tworzone zamiast jednego, jeśli po raz pierwszy łańcuch „hello” jest używany w dowolnym miejscu w programie - jeden w puli łańcuchów ze względu na użycie łańcucha cytowanego i jeden w regularnej przestrzeni sterty, ponieważ użycia nowego słowa kluczowego.

Pula ciągów jest sposobem Java na oszczędzanie pamięci programu poprzez unikanie tworzenia wielu obiektów String zawierających tę samą wartość. Możliwe jest uzyskanie ciągu z puli ciągów dla ciągu utworzonego przy użyciu nowego słowa kluczowego za pomocą metody intern String. Nazywa się to „internowaniem” obiektów łańcuchowych. Na przykład,

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
źródło