Kiedy powinniśmy stosować metodę intern String na literałach String

187

Według String # intern () , internmetoda ma powrócić String z puli String Jeśli łańcuch znajduje się w basenie String, w przeciwnym razie nowy obiekt zostanie dodany ciąg w puli ciąg i odniesienie ten ciąg jest zwracany.

Więc próbowałem tego:

String s1 = "Rakesh";
String s2 = "Rakesh";
String s3 = "Rakesh".intern();

if ( s1 == s2 ){
    System.out.println("s1 and s2 are same");  // 1.
}

if ( s1 == s3 ){
    System.out.println("s1 and s3 are same" );  // 2.
}

Spodziewałem się, że s1 and s3 are samezostanie wydrukowany, ponieważ s3 jest internowany i s1 and s2 are samenie zostanie wydrukowany. Ale wynik jest taki: obie linie są drukowane. Oznacza to, że domyślnie stałe String są internowane. Ale jeśli tak, to dlaczego potrzebujemy tej internmetody? Innymi słowy, kiedy powinniśmy skorzystać z tej metody?

Rakesh Juyal
źródło
14
Javadoc, który połączyłeś, stwierdza również: „Wszystkie ciągi literalne i ciągłe wyrażenia o wartościach ciągowych są internowane”.
Jorn
1
nie jest dokładnym duplikatem ..
Bozho
1
@Jorn: zgadza się. Dlaczego więc mamy interntaką metodę publiczną? Czy nie powinniśmy stosować internmetody prywatnej, aby nikt nie miał do niej dostępu? Czy jest jakiś cel tej metody?
Rakesh Juyal,
2
@RakeshJuyal: Metoda intern jest zdefiniowana na typie ciągu, którym mogą być literały lub zmienne. Jak internowałbyś zmienną, gdyby metoda była prywatna?
bobbyalex

Odpowiedzi:

230

Java automatycznie internalizuje literały łańcuchowe. Oznacza to, że w wielu przypadkach operator == działa na ciągi w taki sam sposób, jak działa na ints lub innych pierwotnych wartościach.

Ponieważ internalizacja jest automatyczna dla literałów łańcuchowych, intern()metodę należy stosować na łańcuchach zbudowanych znew String()

Na twoim przykładzie:

String s1 = "Rakesh";
String s2 = "Rakesh";
String s3 = "Rakesh".intern();
String s4 = new String("Rakesh");
String s5 = new String("Rakesh").intern();

if ( s1 == s2 ){
    System.out.println("s1 and s2 are same");  // 1.
}

if ( s1 == s3 ){
    System.out.println("s1 and s3 are same" );  // 2.
}

if ( s1 == s4 ){
    System.out.println("s1 and s4 are same" );  // 3.
}

if ( s1 == s5 ){
    System.out.println("s1 and s5 are same" );  // 4.
}

wróci:

s1 and s2 are same
s1 and s3 are same
s1 and s5 are same

We wszystkich przypadkach oprócz s4zmiennej wartość, dla której wyraźnie utworzono za pomocąnew operatora, a dla której wyniku internnie użyto metody, jest to pojedyncza niezmienna instancja, która jest zwracana stała pula ciągów JVM .

Aby uzyskać więcej informacji, zobacz JavaTechniques „String Equality and Interning” .

Filipe Miguel Fonseca
źródło
Zakładam, że Java automatycznie internalizuje literały łańcuchowe dla celów optymalizacji. Może to zrobić bezpiecznie tylko dlatego, że ciągi są niezmienne, prawda?
styfle
Nowy w Javie (jestem ze świata C # .NET) i czasami widzę w starszym projekcie Java „” .intern (), więc jeśli dobrze rozumiem, to jest „nonsens” również dla pustych ciągów.
hfrmobile 11.04.13
4
@Miguel Ładne wyjaśnienie, moje pytanie brzmi, w jaki sposób obiekt może powstać tutaj w twoim przykładzie. Oto moje założenie: String s1 = "Rakesh"; pierwszy OB1 String s4 = new String("Rakesh");Drugi OB2 Tak więc reszta (s2, s3, s5) odwołuje się do tego samego obiektu (OB1) utworzonego w „pula ciągów”. Więc mogę powiedzieć, że ta .intern()metoda zastosowana do zapobiegania tworzeniu nowego obiektu jest dostępna, jeśli ten sam ciąg znaków jest dostępny w string poolIf moje założenie jest błędne, więc daj mi kierunek.
HybrisHelp
1
Łącze JavaTechniques jest zepsute
SJuan76
20

W ostatnim projekcie skonfigurowano niektóre ogromne struktury danych z danymi odczytanymi z bazy danych (a zatem nie ze stałymi / literałami ciągów), ale z dużą ilością powielania. Była to aplikacja bankowa, a takie rzeczy jak nazwy skromnego zestawu (może 100 lub 200) korporacji pojawiły się wszędzie. Struktury danych były już duże i gdyby wszystkie te nazwy korpusów były unikatowymi obiektami, przepełniłyby pamięć. Zamiast tego wszystkie struktury danych zawierały odniesienia do tych samych 100 lub 200 obiektów String, co oszczędza dużo miejsca.

Kolejną niewielką zaletą internowanych ciągów jest to, że ==można (z powodzeniem!) Porównać ciągi, jeśli wszystkie zaangażowane ciągi mają gwarancję internowania. Oprócz uproszczonej składni jest to również poprawa wydajności. Ale jak zauważyli inni, zrobienie tego wiąże się z dużym ryzykiem wprowadzenia błędów programistycznych, dlatego powinno się to robić jedynie jako desperacki środek w ostateczności.

Minusem jest to, że internowanie ciągu zajmuje więcej czasu niż zwykłe wyrzucenie go na stos, i że przestrzeń dla internowanych ciągów może być ograniczona, w zależności od implementacji Java. Najlepiej zrobić to, gdy masz do czynienia ze znaną rozsądną liczbą ciągów z wieloma duplikacjami.

Carl Smotricz
źródło
@ The downside is that interning a String takes more time than simply throwing it on the heap, and that the space for interned Strings may be limitednawet jeśli nie użyjesz metody intern dla stałej String, zostanie internowana automatycznie.
Rakesh Juyal,
2
@Rakesh: Zwykle nie ma tak wielu stałych String w danej klasie, więc nie jest to problem przestrzeni / czasu ze stałymi.
David Rodríguez - dribeas
Tak, komentarz Rakesha nie ma zastosowania, ponieważ internowanie Ciągów odbywa się (jawnie) tylko w przypadku Ciągów, które są w jakiś sposób „generowane”, czy to przez manipulację wewnętrzną, czy przez pobieranie danych z bazy danych. W przypadku stałych nie mamy wyboru.
Carl Smotricz
2
+1. Myślę, że to dobry przykład, kiedy internowanie ma sens. Nie zgadzam się jednak ==na łańcuchy.
Alexander Pogrebnyak,
1
Począwszy od Java 7, „pula ciągów” jest implementowana w przestrzeni sterty, dzięki czemu ma wszystkie zalety przechowywania stażystów, odśmiecania, a jej wielkość nie jest ograniczona, można ją zwiększyć do wielkości sterty. (Nigdy nie będziesz potrzebować aż tyle pamięć ciągów)
Anil Uttani,
15

Chcę dodać moje 2 centy za używanie ==z internowanymi łańcuchami.

Pierwszą rzeczą String.equalsjest this==object.

Więc chociaż istnieje niewielki wzrost wydajności (nie wywołujesz metody), z punktu widzenia opiekuna używanie ==jest koszmarem, ponieważ niektóre internowane łańcuchy mają tendencję do nie internowania.

Dlatego sugeruję, aby nie polegać na specjalnym przypadku ==internowanych łańcuchów, ale zawsze używaj equalszgodnie z zamierzeniami Goslinga.

EDYCJA: internowany staje się internowany:

V1.0
public class MyClass
{
  private String reference_val;

  ...

  private boolean hasReferenceVal ( final String[] strings )
  {
    for ( String s : strings )
    {
      if ( s == reference_val )
      {
        return true;
      }
    }

    return false;
  }

  private void makeCall ( )
  {
     final String[] interned_strings =  { ... init with interned values ... };

     if ( hasReference( interned_strings ) )
     {
        ...
     }
  }
}

W wersji 2.0 opiekun postanowił hasReferenceValupublicznić, nie wchodząc w szczegóły, że oczekuje szeregu internowanych ciągów.

V2.0
public class MyClass
{
  private String reference_val;

  ...

  public boolean hasReferenceVal ( final String[] strings )
  {
    for ( String s : strings )
    {
      if ( s == reference_val )
      {
        return true;
      }
    }

    return false;
  }

  private void makeCall ( )
  {
     final String[] interned_strings =  { ... init with interned values ... };

     if ( hasReference( interned_strings ) )
     {
        ...
     }
  }
}

Teraz masz błąd, który może być bardzo trudny do znalezienia, ponieważ w większości przypadków tablica zawiera wartości dosłowne, a czasami używany jest łańcuch nieliteralny. Jeśli equalsużyto zamiast ==wtedy hasReferenceValmusiałby nadal kontynuować pracę. Po raz kolejny wzrost wydajności jest niewielki, ale koszty utrzymania są wysokie.

Alexander Pogrebnyak
źródło
„niektóre internowane łańcuchy mają tendencję do pozostania nie internowanym”. wow, to byłoby ... dziwne. Czy możesz zacytować referencję?
Carl Smotricz,
2
OK, myślałem, że odnosisz się do Strun wychodzących z puli stażystów na stos dzięki magii w JVM. Mówisz, że == zwiększa prawdopodobieństwo wystąpienia pewnych klas błędów programisty.
Carl Smotricz,
„Dlatego sugeruję, aby nie polegać na specjalnym przypadku == dla internowanych ciągów, ale zawsze używaj równości zgodnie z zamierzeniami Goslinga.” Czy masz na ten temat bezpośredni cytat lub komentarz od Goslinga? Jeśli tak jest, dlaczego w ogóle zadał sobie trud umieszczenia intern () i użycia == w języku?
1
intern nie nadaje się do bezpośredniego porównania (==), nawet jeśli działa, jeśli oba łańcuchy są internowane. wspaniale jest obniżyć całkowitą ilość używanej pamięci: gdy ten sam ciąg znaków jest używany w więcej niż 1 miejscu.
tgkprog 30.04.2013
12

Literały ciągowe i stałe są domyślnie internowane. To znaczy "foo" == "foo"(zadeklarowane przez literały ciągów), ale new String("foo") != new String("foo").

Bozho
źródło
4
Więc pytanie jest, kiedy powinniśmy używać intern,
Rakesh Juyal
wskazano na stackoverflow.com/questions/1833581/when-to-use-intern i szereg innych pytań, niektóre z nich z wczoraj.
Bozho,
Daj mi znać, jeśli rozumiem to stwierdzenie String literals and constants are interned by default:, jest poprawne. new String("foo")-> Tutaj, jeden literał „Foo” napisany jest w puli napisów, a drugi w stercie, więc powstają łącznie 2 obiekty.
dkb
8

Naucz się Java String Intern - raz na zawsze

Łańcuchy w Javie są niezmiennymi obiektami z założenia. Dlatego dwa obiekty łańcuchowe nawet o tej samej wartości będą domyślnie różnymi obiektami. Jeśli jednak chcemy zaoszczędzić pamięć, możemy wskazać na użycie tej samej pamięci przez koncepcję zwaną intern intern.

Poniższe zasady pomogą ci zrozumieć pojęcie w sposób jasny:

  1. Klasa string utrzymuje wewnętrzną pulę, która początkowo jest pusta. Ta pula musi gwarantować, że będzie zawierać obiekty łańcuchowe zawierające tylko unikalne wartości.
  2. Wszystkie literały łańcuchowe o tej samej wartości muszą być uważane za ten sam obiekt lokalizacji pamięci, ponieważ w przeciwnym razie nie miałyby pojęcia rozróżnienia. Dlatego wszystkie takie literały o tej samej wartości dokonają pojedynczego wpisu w puli wewnętrznej i będą odnosić się do tej samej lokalizacji pamięci.
  3. Łączenie dwóch lub więcej literałów jest również dosłowne. (Dlatego zasada nr 2 będzie do nich stosowana)
  4. Każdy ciąg utworzony jako obiekt (tj. Dowolną inną metodą oprócz dosłownej) będzie miał inne lokalizacje w pamięci i nie będzie zawierał żadnych wpisów w puli wewnętrznej
  5. Łączenie literałów z literałami niebędącymi literałami spowoduje, że literały nie będą literalne. Zatem wynikowy obiekt będzie miał nową lokalizację w pamięci i NIE będzie dokonywał wpisu w puli wewnętrznej.
  6. Wywołanie metody intern na obiekcie łańcuchowym powoduje utworzenie nowego obiektu, który wchodzi do puli wewnętrznej lub zwrócenie istniejącego obiektu z puli o tej samej wartości. Wywołanie dowolnego obiektu, który nie znajduje się w wewnętrznej puli, NIE przenosi obiektu do puli. Raczej tworzy inny obiekt, który wchodzi do puli.

Przykład:

String s1=new String (“abc”);
String s2=new String (“abc”);
If (s1==s2)  //would return false  by rule #4
If (“abc == a”+”bc )  //would return true by rules #2 and #3
If (“abc == s1 )  //would return false  by rules #1,2 and #4
If (“abc == s1.intern() )  //would return true  by rules #1,2,4 and #6
If ( s1 == s2.intern() )      //wound return false by rules #1,4, and #6

Uwaga: Przypadki motywacyjne dla intern stringów nie są tutaj omawiane. Jednak oszczędzanie pamięci z pewnością będzie jednym z głównych celów.

KGhatak
źródło
Dziękuję za # 3, nie wiedziałem :)
kaay 15'19
4

powinieneś wskazać dwa okresy, które są czasem kompilacji i czasem wykonania. na przykład:

//example 1 
"test" == "test" // --> true 
"test" == "te" + "st" // --> true

//example 2 
"test" == "!test".substring(1) // --> false
"test" == "!test".substring(1).intern() // --> true

z jednej strony, w przykładzie 1, okazuje się, że wszystkie wyniki zwracają wartość true, ponieważ w czasie kompilacji jvm umieści „test” w puli literałów, jeśli jvm znajdzie „test”, to wtedy użyje istniejącego, w przykładzie 1, łańcuchy „testowe” wskazują na ten sam adres pamięci, więc przykład 1 zwróci true. z drugiej strony w przykładzie 2 metoda substring () jest wykonywana w czasie wykonywania, w przypadku „test” == ”! test” .substring (1) pula utworzy obiekt o dwóch ciągach, „ test ”i„! test ”, więc są to różne obiekty referencyjne, więc ten przypadek zwróci false, w przypadku„ test ”==”! test ”.substring (1) .intern (), metoda intern ( ) umieści „„! test ”.substring (1)” w puli literałów,

thinkinjava
źródło
3

http://en.wikipedia.org/wiki/String_interning

internowanie ciągów to metoda przechowywania tylko jednej kopii każdej odrębnej wartości ciągu, która musi być niezmienna. Wewnętrzne łańcuchy sprawiają, że niektóre zadania przetwarzania łańcucha są bardziej czasochłonne lub zajmują mało miejsca, co kosztuje więcej czasu, gdy łańcuch jest tworzony lub internowany. Różne wartości są przechowywane w wewnętrznej puli ciągów.

bbcbcgb
źródło
2

Internowane ciągi unikają duplikatów ciągów. Interning oszczędza pamięć RAM kosztem więcej czasu procesora na wykrycie i zastąpienie zduplikowanych ciągów. Istnieje tylko jedna kopia każdego ciągu, który został internowany, bez względu na to, ile odwołań do niego wskazuje. Ponieważ ciągi są niezmienne, jeśli dwie różne metody przypadkowo wykorzystują ten sam ciąg, mogą udostępniać kopię tego samego ciągu. Proces konwersji zduplikowanych ciągów na współdzielone nazywa się interning.String.intern () daje adres kanonicznego wzorca String. Możesz porównać internowane ciągi z prostym == (który porównuje wskaźniki) zamiast z równymktóry porównuje znaki ciągu jeden po drugim. Ponieważ ciągi są niezmienne, proces internowania pozwala na dalsze oszczędzanie miejsca, na przykład nie tworząc osobnego literału łańcuchowego dla „puli”, gdy istnieje jako podciąg innego literału, takiego jak „hipopotam”.

Aby zobaczyć więcej http://mindprod.com/jgloss/interned.html

inter18099
źródło
2
String s1 = "Anish";
        String s2 = "Anish";

        String s3 = new String("Anish");

        /*
         * When the intern method is invoked, if the pool already contains a
         * string equal to this String object as determined by the
         * method, then the string from the pool is
         * returned. Otherwise, this String object is added to the
         * pool and a reference to this String object is returned.
         */
        String s4 = new String("Anish").intern();
        if (s1 == s2) {
            System.out.println("s1 and s2 are same");
        }

        if (s1 == s3) {
            System.out.println("s1 and s3 are same");
        }

        if (s1 == s4) {
            System.out.println("s1 and s4 are same");
        }

WYNIK

s1 and s2 are same
s1 and s4 are same
anish
źródło
2
String p1 = "example";
String p2 = "example";
String p3 = "example".intern();
String p4 = p2.intern();
String p5 = new String(p3);
String p6 = new String("example");
String p7 = p6.intern();

if (p1 == p2)
    System.out.println("p1 and p2 are the same");
if (p1 == p3)
    System.out.println("p1 and p3 are the same");
if (p1 == p4)
    System.out.println("p1 and p4 are the same");
if (p1 == p5)
    System.out.println("p1 and p5 are the same");
if (p1 == p6)
    System.out.println("p1 and p6 are the same");
if (p1 == p6.intern())
    System.out.println("p1 and p6 are the same when intern is used");
if (p1 == p7)
    System.out.println("p1 and p7 are the same");

Gdy dwa ciągi są tworzone niezależnie, intern() pozwala je porównać, a także pomaga w tworzeniu odwołania w puli ciągów, jeśli odwołanie nie istniało wcześniej.

Gdy używasz String s = new String(hi), Java tworzy nową instancję ciągu, ale kiedy używaszString s = "hi" , java sprawdza, czy w kodzie jest wystąpienie słowa „cześć”, a jeśli istnieje, po prostu zwraca odwołanie.

Ponieważ porównywanie ciągów jest oparte na odwołaniu, intern()pomaga w tworzeniu odniesienia i pozwala na porównanie zawartości ciągów.

Kiedy użyjesz intern()w kodzie, to wyczyści miejsce używane przez ciąg znaków odnoszący się do tego samego obiektu i po prostu zwraca odwołanie do już istniejącego tego samego obiektu w pamięci.

Ale w przypadku p5, gdy używasz:

String p5 = new String(p3);

Kopiowana jest tylko zawartość p3, a p5 jest nowo tworzone. Więc nie jest internowany .

Więc wynik będzie:

p1 and p2 are the same
p1 and p3 are the same
p1 and p4 are the same
p1 and p6 are the same when intern is used
p1 and p7 are the same
raja emani
źródło
2
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    String s1 = "test";
    String s2 = new String("test");
    System.out.println(s1==s2);              //false
    System.out.println(s1==s2.intern());    //true --> because this time compiler is checking from string constant pool.
}
Anurag_BEHS
źródło
1

Metoda string intern () służy do utworzenia dokładnej kopii obiektu ciągu stosu w stałej puli ciągów. Obiekty ciągów w puli stałych ciągów są automatycznie internowane, ale obiekty ciągów w stercie nie są. Głównym zastosowaniem tworzenia stażystów jest oszczędzanie miejsca w pamięci i szybsze porównywanie obiektów łańcuchowych.

Źródło: Co to jest stażysta w java?

użytkownik2485429
źródło
1

Jak powiedziałeś, ta intern()metoda ciągu najpierw znajdzie z puli String, jeśli ją znajdzie, zwróci obiekt wskazujący na nią lub doda nowy ciąg do puli.

    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = "Hello".intern();
    String s4 = new String("Hello");

    System.out.println(s1 == s2);//true
    System.out.println(s1 == s3);//true
    System.out.println(s1 == s4.intern());//true

s1I s2są dwa obiekty wskazujące na basenie ciąg „Hello” i używając "Hello".intern()przekonają się, że s1i s2. Więc "s1 == s3"zwraca prawdę, a także do s3.intern().

WendellWu
źródło
To tak naprawdę nie dostarcza nowych informacji. Jest już wyjątek.
Alexander
0

Używając odwołania do obiektu sterty, jeśli chcemy uzyskać odpowiednie odniesienie do obiektu stałej puli ciągów znaków , powinniśmy skorzystać z intern ()

String s1 = new String("Rakesh");
String s2 = s1.intern();
String s3 = "Rakesh";

System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

Widok obrazkowy wprowadź opis zdjęcia tutaj

Krok 1: Obiekt z danymi „Rakesh” zostaje utworzony w puli stałej stosu i ciągu. Również s1 zawsze wskazuje na obiekt sterty.

Krok 2: Korzystając z referencji do obiektu sterty s1, staramy się uzyskać odpowiedni referencyjny obiekt puli stałej ciągu sc2 za pomocą intern ()

Krok 3: Celowe utworzenie obiektu z danymi „Rakesh” w stałej puli ciągów, do której odwołuje się nazwa s3

Jako operator „==” przeznaczony do porównania.

Pierwsze false dla s1 == s2

Pierwsze prawdziwe dla s2 s3 ==

Mam nadzieję, że to pomoże!!

Km
źródło