Czy dobrą praktyką jest używanie java.lang.String.intern ()?


194

Javadoc o String.intern()nie podaje zbyt wielu szczegółów. (W skrócie: Zwraca kanoniczną reprezentację ciągu, umożliwiając porównanie internowanych ciągów za pomocą ==)

  • Kiedy powinienem użyć tej funkcji na korzyść String.equals()?
  • Czy są jakieś skutki uboczne niewymienione w Javadoc, tj. Mniej więcej optymalizacja przez kompilator JIT?
  • Czy są dalsze zastosowania String.intern()?

14
Wywołanie intern () ma swój wpływ na działanie, użycie intern () w celu poprawy wydajności musi zostać przetestowane, aby upewnić się, że naprawdę przyspiesza Twój program i jest warte dodatkowej złożoności. Można to również wykorzystać do zmniejszenia zużycia pamięci dla dużych tabel z wartościami repedatywnymi. Jednak w obu przypadkach istnieją inne opcje, które mogą być lepsze.
Peter Lawrey

Tak, intern () ma swój wpływ na wydajność. Zwłaszcza, że ​​koszt intern () wzrasta liniowo, gdy internujesz łańcuchy i zachowujesz do nich odniesienie. Przynajmniej na słońcu / wyroczni 1.6.0_30 vm.
lacroix1547

Odpowiedzi:


125

Kiedy powinienem użyć tej funkcji na rzecz String.equals ()

kiedy potrzebujesz prędkości, ponieważ możesz porównywać ciągi przez odniesienie (== jest szybszy niż równy)

Czy są jakieś działania niepożądane nie wymienione w Javadoc?

Podstawową wadą jest to, że musisz pamiętać, aby upewnić się, że faktycznie wykonujesz intern () wszystkich ciągów, które zamierzasz porównać. Łatwo zapomnieć o intern () wszystkich ciągach, dzięki czemu można uzyskać myląco niepoprawne wyniki. Ponadto, dla dobra wszystkich, bardzo wyraźnie udokumentuj, że polegasz na internalizowanych ciągach.

Drugą wadą, jeśli zdecydujesz się na internalizację łańcuchów, jest to, że metoda intern () jest stosunkowo droga. Musi zarządzać pulą unikatowych ciągów, więc wykonuje sporo pracy (nawet jeśli ciąg został już internalizowany). Bądź więc ostrożny przy projektowaniu kodu, aby np. Intern () wszystkie odpowiednie ciągi wejściowe, abyś nie musiał się już o to martwić.

(od JGuru)

Trzecia wada (tylko Java 7 lub mniej): internowane ciągi żyją w przestrzeni PermGen, która zwykle jest dość mała; możesz napotkać OutOfMemoryError z dużą ilością wolnego miejsca na stercie.

(od Michael Borgwardt)


64
Trzecia wada: internowane Struny żyją w przestrzeni PermGen, która zwykle jest dość mała; możesz napotkać OutOfMemoryError z dużą ilością wolnego miejsca na stercie.
Michael Borgwardt,

15
Nowsze maszyny wirtualne AFAIK również wyrzucają śmieci do przestrzeni PermGen.
Daniel Rikowski

31
Stażysta dotyczy zarządzania pamięcią, a nie szybkości porównywania. Różnica między if (s1.equals(s2))i if (i1 == i2)jest minimalna, chyba że masz wiele długich łańcuchów z tymi samymi wiodącymi postaciami. W większości rzeczywistych zastosowań (innych niż adresy URL) ciągi znaków będą się różnić w pierwszych kilku znakach. Długie łańcuchy if-else i tak pachną kodem: używaj wyliczeń i map funktorów.
kdgregory

25
nadal można używać składni s1.equals w całym programie, nie używaj ==, == .equals używać wewnętrznie do oceny zwarcia
gtrak

15
Michael Borgwardt NIE powiedział, że internowanych łańcuchów nie można wyrzucać. I to jest NIEPRAWIDŁOWE stwierdzenie. To, co mówią Michael (poprawnie), jest bardziej subtelne.
Stephen C

193

Nie ma (prawie) nic wspólnego z porównywaniem ciągów. Interningowanie ciągów służy do oszczędzania pamięci, jeśli w aplikacji jest wiele ciągów o tej samej treści. Korzystanie String.intern()z aplikacji będzie miało tylko jedną instancję w długim okresie, a efektem ubocznym jest to, że możesz wykonać szybkie porównanie równości referencji zamiast zwykłego porównania ciągów (ale zwykle nie jest to wskazane, ponieważ bardzo łatwo jest je przerwać, zapominając o internowaniu pojedyncza instancja).


4
To nie tak. Internowanie ciągów następuje zawsze automatycznie, gdy każde wyrażenie łańcuchowe jest oceniane. Zawsze jest jedna kopia na każdy użyty ciąg znaków i jest ona „wewnętrznie współdzielona”, jeśli wystąpi wiele zastosowań. Wywołanie String.intern () nie sprawia, że ​​tak się dzieje - po prostu zwraca wewnętrzną reprezentację kanoniczną. Zobacz javadoc.
Glen Best

16
Trzeba wyjaśnić - internowanie zawsze odbywa się automatycznie dla ciągów stałych kompilacji (literały i wyrażenia stałe). Dodatkowo występuje, gdy String.intern () jest wywoływany w środowisku wykonawczym dynamicznie ocenianych ciągów.
Glen Best

Masz na myśli, że jeśli w Heap znajduje się 1000 obiektów „Cześć” i wykonam intern () na jednym z nich, to 999 obiektów zostanie automatycznie zniszczonych?
Arun Raaj

@ArunRaaj nie, nadal będziesz mieć 1000 na stosie, a dodatkowo jeden w puli stażystów, który może być gotowy do ponownego użycia, str.intern()kiedy strbędzie "Hello".
Matthieu

37

String.intern()jest zdecydowanie śmieciami gromadzonymi w nowoczesnych maszynach JVM.
Następujące NIGDY nie zabraknie pamięci z powodu aktywności GC:

// java -cp . -Xmx128m UserOfIntern

public class UserOfIntern {
    public static void main(String[] args) {
        Random random = new Random();
        System.out.println(random.nextLong());
        while (true) {
            String s = String.valueOf(random.nextLong());
            s = s.intern();
        }
    }
}

Zobacz więcej (ode mnie) na temat mitu non GCed String.intern () .


26
OutOfMemoryException- nie, nie powyższy kod, w moim mózgu : link do artykułu javaturning, który wskazuje na ten artykuł, który wskazuje na artykuł javaturning, który ... :-)
user85421

Chociaż widać, że post został zredagowany, aby dodać ten link;)
Riking

3
Możesz wspomnieć, że jesteś także autorem odnośnika zewnętrznego, do którego linkujesz.
Thorbjørn Ravn Andersen

11
@Carlos łączący odnośnik zewnętrzny, który prowadzi z powrotem do stackoverflow, powinien spowodować ..
Stackoverflow

2
@Seiti Okólniki można łatwo wykryć w dzisiejszych czasach: p
Ajay,

16

Niedawno napisałem artykuł o implementacji String.intern () w Javie 6, 7 i 8: String.intern w Javie 6, 7 i 8 - ciąg znaków .

Mam nadzieję, że powinien on zawierać wystarczającą ilość informacji o bieżącej sytuacji z pulowaniem ciągów w Javie.

W skrócie:

  • Unikaj String.intern()w Javie 6, ponieważ wchodzi ona w PermGen
  • Preferuj String.intern()w Javie 7 i Javie 8: używa 4-5x mniej pamięci niż tworzenie własnej puli obiektów
  • Pamiętaj, aby nastroić -XX:StringTableSize(domyślnie jest prawdopodobnie zbyt mały; ustaw liczbę pierwszą)

3
Nie publikuj tylko linków do swojego bloga, przez niektórych jest to spam. Dodatkowo linki do blogów mają zauważalną tendencję do śmierci 404. W tym miejscu proszę streścić swój artykuł lub pozostawić ten link w komentarzu do pytania.
Mat

3
Dzięki za napisanie, że @ mik1! Bardzo pouczający, jasny i aktualny artykuł. (Wróciłem tutaj z zamiarem opublikowania linku do niego.)
Luke Usherwood,

1
Dzięki za wzmiankę o argsie -XX. Możesz także użyć tego, aby wyświetlić statystyki tabeli: -XX: +
PrintStringTableStatistics

13

Porównywanie ciągów z == jest znacznie szybsze niż z equals ()

5 Czas szybszy, ale ponieważ porównanie ciągów zwykle stanowi tylko niewielki procent całkowitego czasu wykonania aplikacji, całkowite wzmocnienie jest znacznie mniejsze, a końcowe wzmocnienie zostanie zmniejszone do kilku procent.

String.intern () wyciągnij ciąg z Heap i umieść go w PermGen

Zinternalizowane łańcuchy są umieszczane w innym obszarze przechowywania: Stałe generowanie, które jest obszarem JVM zarezerwowanym dla obiektów innych niż użytkownik, takich jak klasy, metody i inne wewnętrzne obiekty JVM. Wielkość tego obszaru jest ograniczona i jest o wiele cenniejsza niż kupa. Ponieważ obszar ten jest mniejszy niż sterty, istnieje większe prawdopodobieństwo, że wykorzystasz całą przestrzeń i uzyskasz wyjątek OutOfMemoryException.

Ciąg String.intern () jest śmieciami

W nowych wersjach JVM również zinternalizowany ciąg jest zbierany, gdy żaden obiekt go do niego nie odwołuje.

Pamiętając o powyższym 3 punkcie, można wywnioskować, że String intern () może być użyteczny tylko w nielicznych sytuacjach, gdy wykonujesz dużo porównań ciągów, jednak lepiej nie używać wewnętrznego ciągu, jeśli nie wiesz dokładnie, co robią ...



1
Wystarczy dodać, że czasami można odzyskać wyjątki pamięci sterty, szczególnie w modelach wątkowych, takich jak aplikacje internetowe. Kiedy permgen zostanie wyczerpany, aplikacja zazwyczaj będzie trwale niefunkcjonalna i często będzie powodowała przeładowanie zasobami aż do zabicia.
Taylor,

7

Kiedy powinienem użyć tej funkcji na rzecz String.equals ()

Biorąc pod uwagę, że robią różne rzeczy, prawdopodobnie nigdy.

Interningowanie łańcuchów ze względu na wydajność, aby można je było porównać pod kątem równości referencji, przyniesie korzyść tylko wtedy, gdy będziesz trzymał odniesienia do łańcuchów przez jakiś czas - łańcuchy pochodzące z danych wprowadzonych przez użytkownika lub IO nie zostaną internowane.

Oznacza to, że w Twojej aplikacji otrzymujesz dane wejściowe z zewnętrznego źródła i przetwarzasz je na obiekt o wartości semantycznej - jak mówią identyfikatory - ale ten obiekt ma typ nierozróżnialny od surowych danych i ma inne reguły dotyczące tego, jak programista powinien Użyj tego.

Prawie zawsze lepiej jest stworzyć UserIdtyp, który jest internowany (łatwo jest stworzyć bezpieczny dla wątków ogólny mechanizm internowania) i działa jak otwarty wylicznik, niż przeciążać java.lang.Stringtyp semantyką odniesienia, jeśli jest to identyfikator użytkownika.

W ten sposób nie będziesz mylony z tym, czy dany łańcuch został internowany, i możesz zawrzeć dowolne dodatkowe zachowanie wymagane w otwartym wyliczeniu.


6

Nie jestem świadomy żadnych zalet, a gdyby istniał, pomyślałby, że equals () sam użyłby wewnętrznie () wewnętrznie (czego nie robi).

Obalanie mitów intern ()


7
Mimo, że powiedziałeś, że nie jesteś świadomy żadnych korzyści, opublikowany link identyfikuje porównanie za pomocą == jako 5-krotnie szybsze, a tym samym ważne dla kodu wykonującego zorientowanego na tekst
Brian Agnew

3
Kiedy będziesz musiał zrobić wiele porównywania tekstu, w końcu zabraknie ci miejsca w PermGen. Gdy nie ma tyle porównywania tekstu, różnica prędkości nie ma znaczenia. Tak czy inaczej, po prostu nie intern () swoich ciągów. To nie jest tego warte.
Bombe,

Dalej mówi się również, że ogólny zysk względny zwykle będzie niewielki.
Przedmioty

Nie sądzę, aby tego rodzaju logika była poprawna. Dobry link!
Daniel Rikowski,

1
@DR: jaka logika? To jeden wielki błąd. @objects: przepraszam, ale twoje argumenty nie mają uzasadnienia. Są bardzo dobre powody do użycia interni bardzo dobre powody, equalsktóre domyślnie tego nie robią. Link, który opublikowałeś, to kompletne pierdoły. Ostatni akapit nawet przyznaje, że internma prawidłowy scenariusz użycia: intensywne przetwarzanie tekstu (np. Parser). Stwierdzenie, że „[XYZ] jest niebezpieczne, jeśli nie wiesz, co robisz” jest tak banalne, że fizycznie boli.
Konrad Rudolph

4

Daniel Brückner ma absolutną rację. Internowanie ciągów ma na celu oszczędzanie pamięci (sterty). Nasz system ma obecnie gigantyczną mapę do przechowywania niektórych danych. W miarę skalowania systemu, mapa będzie wystarczająco duża, aby usunąć stertę z pamięci (jak testowaliśmy). Dzięki internalizacji wszystkich zduplikowanych ciągów wszystkich obiektów w haszapie oszczędza nam to znaczną ilość miejsca na stercie.

Również w Javie 7 internowane łańcuchy już nie żyją w PermGen, ale zamiast tego stos. Więc nie musisz się martwić o jego rozmiar i tak, zbiera śmieci:

W JDK 7 internowane ciągi nie są już przydzielane w stałej generacji sterty Java, ale zamiast tego są przydzielane 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 stałej generacji, 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 (), zauważą bardziej znaczące różnice.


Muszę po drugie: w moim oprogramowaniu zrzut sterty wykazał, że większość miejsca na sterty była używana przez Stringinstancje. Patrząc na ich zawartość, zobaczyłem wiele duplikatów i postanowiłem przejść na intern(), co pozwoliło zaoszczędzić setki MB.
Matthieu,

4

Czy są jakieś skutki uboczne niewymienione w Javadoc, tj. Mniej więcej optymalizacja przez kompilator JIT?

Nie wiem o poziomie JIT, ale istnieje bezpośrednia obsługa kodu bajtowego dla puli ciągów , która jest magicznie i wydajnie implementowana za pomocą dedykowanej CONSTANT_String_infostruktury (w przeciwieństwie do większości innych obiektów, które mają bardziej ogólne reprezentacje).

JVMS

JVMS 7 5.1 mówi :

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

Warto również przyjrzeć się implementacji kodu bajtowego w OpenJDK 7.

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

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


2

Zbadałbym intern i == - porównanie zamiast równości tylko w przypadku, gdy porównanie równości jest wąskim gardłem w wielu porównaniach łańcucha. Jest mało prawdopodobne, aby pomogło to w niewielkiej liczbie porównań, ponieważ intern () nie jest darmowy. Po agresywnym internowaniu ciągów będziesz coraz wolniej wywoływać wywołania intern ().


2

Rodzaj wycieku pamięci może wynikać z użycia, subString()gdy wynik jest niewielki w porównaniu do ciągu źródłowego, a obiekt ma długą żywotność.

Normalnym rozwiązaniem jest użycie, new String( s.subString(...))ale gdy masz klasę, która przechowuje wynik potencjalnego / prawdopodobnego subString(...)i nie masz kontroli nad dzwoniącym, możesz rozważyć zapisanie intern()argumentów String przekazanych do konstruktora. To zwalnia potencjalnie duży bufor.


Ciekawe, ale być może zależy to od implementacji.
akostadinov

1
Wyżej wspomniany potencjalny wyciek pamięci nie występuje w java 1.8 i 1.7.06 (i nowsze) patrz zmiany do wewnętrznej reprezentacji String wykonane w Javie 1.7.0_06 .
eremmel

potwierdzający, że mikrooptymalizacje należy stosować tylko wtedy, gdy jest to konieczne po profilowaniu wydajności i / lub pamięci. Dziękuję Ci.
akostadinov

2

Internowanie łańcuchów jest przydatne w przypadku, gdy equals()metoda jest często wywoływana, ponieważ equals()metoda szybko sprawdza, czy obiekty są takie same na początku metody.

if (this == anObject) {
    return true;
}

Zdarza się to zwykle podczas przeszukiwania Collectioninnego kodu, ale może również sprawdzać równość łańcuchów.

Jednak internowanie wiąże się z pewnymi kosztami. Wykonałem znak mikrodrobny jakiegoś kodu i stwierdziłem, że proces internowania wydłuża czas działania o współczynnik 10.

Najlepszym miejscem do przeprowadzenia internowania jest zwykle odczytywanie kluczy przechowywanych poza kodem, ponieważ ciągi znaków w kodzie są automatycznie internowane. Zwykle dzieje się tak na etapach inicjalizacji aplikacji, aby uniknąć kary pierwszego użytkownika.

Innym miejscem, w którym można to zrobić, jest przetwarzanie danych wejściowych użytkownika, które można wykorzystać do wyszukiwania kluczy. Zwykle dzieje się tak w twoim procesorze żądań, zwróć uwagę, że internowane ciągi powinny być przekazywane.

Poza tym internowanie w pozostałej części kodu nie ma większego sensu, ponieważ generalnie nie przyniesie żadnych korzyści.


1

Głosowałbym za tym, że nie jest to warte kłopotów z utrzymaniem.

Przez większość czasu nie będzie potrzeby ani korzyści w zakresie wydajności, chyba że Twój kod wykonuje wiele pracy z podciągami. W takim przypadku klasa String użyje oryginalnego ciągu plus przesunięcie w celu zaoszczędzenia pamięci. Jeśli twój kod często używa podciągów, podejrzewam, że spowoduje to eksplozję wymagań dotyczących pamięci.


1

http://kohlerm.blogspot.co.uk/2009/01/is-javalangstringintern-really-evil.html

zapewnia, że String.equals()używa wcześniej "=="do porównywania Stringobiektów, zgodnie z

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

porównuje długości ciągów, a następnie zawartość.

(Nawiasem mówiąc, ciągi kodu produktu w katalogu sprzedaży mogą mieć tę samą długość - BIC0417 to kask bezpieczeństwa rowerzysty, TIG0003 to żywy dorosły tygrys męski - prawdopodobnie potrzebujesz różnych licencji, aby zamówić jeden z nich. I może lepiej zamówić kask ochronny w tym samym czasie.)

Brzmi więc to tak, jakbyś zyskał na zamianie ciągów na ich intern()wersję, ale zyskujesz bezpieczeństwo - oraz czytelność i zgodność z normami - bez użycia „==” do equals()programowania. I większość tego, co powiem, zależy od tego, czy to prawda, jeśli to prawda.

Ale czy String.equals()test, czy przed użyciem przekazałeś mu String, a nie jakiś inny obiekt "=="? Nie jestem uprawniony do powiedzenia, ale zgaduję, że nie, ponieważ w przeważającej większości większość takich equals()operacji będzie przetwarzanych na ciąg znaków, dzięki czemu test prawie zawsze jest zaliczany. Rzeczywiście, nadanie priorytetu wewnątrz „==” String.equals()implikuje pewność, że często porównujesz Łańcuch z tym samym rzeczywistym obiektem.

Mam nadzieję, że nikt nie jest zaskoczony, że następujące wiersze dają wynik „fałszu”:

    Integer i = 1;
    System.out.println("1".equals(i));

Ale jeśli zmienisz ina i.toString()w drugiej linii, oczywiście, że tak true.

Miejsca, w których możesz spodziewać się korzyści z internowania, to Seti Mapoczywiście. Mam nadzieję, że internowane łańcuchy mają buforowane kody hash ... Myślę, że to byłby wymóg. I mam nadzieję, że nie zdradziłem tylko pomysłu, który mógłby zarobić milion dolarów. :-)

Jeśli chodzi o pamięć, oczywiste jest również, że jest to ważny limit, jeśli wolumen napisów jest duży lub jeśli chcesz, aby pamięć używana przez kod programu była bardzo mała. Jeśli twoja objętość -różniących-Ciągów jest bardzo duża, być może nadszedł czas, aby rozważyć użycie dedykowanego kodu programu bazy danych do zarządzania nimi oraz osobnego serwera bazy danych. Podobnie, jeśli możesz ulepszyć mały program (który musi działać jednocześnie w 10000 instancjach), nie zapisując w ogóle swoich ciągów.

Tworzenie nowego ciągu znaków, a następnie odrzucanie go jako intern()substytutu, wydaje się marnotrawstwem , ale nie ma wyraźnej alternatywy, z wyjątkiem zachowania duplikatu ciągu. Tak naprawdę koszt wykonania polega na wyszukaniu łańcucha w puli wewnętrznej, a następnie umożliwieniu śmieciarzowi usunięcia oryginału. A jeśli jest to dosłowny ciąg, to i tak jest już internowany.

Zastanawiam się, czy intern()złośliwy kod programu może nadużyć, aby wykryć, czy niektóre ciągi i ich odwołania do obiektów już istnieją w intern()puli, a zatem istnieją gdzie indziej w sesji Java, kiedy nie powinno to być znane. Ale byłoby to możliwe tylko wtedy, gdy kod programu jest już używany w zaufany sposób. Nadal warto wziąć pod uwagę biblioteki innych firm, które dołączasz do swojego programu, aby przechowywać i zapamiętywać numery PIN do bankomatów!


0

Prawdziwy powód do korzystania z internu nie jest powyższy. Możesz go użyć po wystąpieniu błędu braku pamięci. Dużo łańcucha w typowym programie to String.substring () innego dużego łańcucha [pomyśl o wyjęciu nazwy użytkownika z pliku 100ml xml. Implementacja Java jest taka, że ​​podłańcuch zawiera odniesienie do oryginalnego łańcucha, a początek + koniec w tym ogromnym łańcuchu. (Myśl, która się za tym kryje, to ponowne użycie tego samego dużego łańcucha)

Po 1000 dużych plików, z których zapisujesz tylko 1000 krótkich nazw, zachowasz w pamięci całe 1000 plików! Rozwiązanie: w tym scenariuszu wystarczy użyć smallsubstring.intern ()


Dlaczego po prostu nie utworzyć nowego ciągu znaków z podłańcucha, jeśli go potrzebujesz?
Thorbjørn Ravn Andersen

0

Używam intern do oszczędzania pamięci, trzymam dużą ilość danych String w pamięci i przechodząc do użycia intern () zaoszczędziłem ogromną ilość pamięci. Niestety, chociaż wykorzystuje dużo mniej pamięci, pamięć, której używa, jest przechowywana w pamięci PermGen, a nie w Heap, i trudno jest wyjaśnić klientom, jak zwiększyć przydział tego rodzaju pamięci.

Czy istnieje alternatywa dla intern () w celu zmniejszenia zużycia pamięci ((== versus równa korzyści wydajności nie jest dla mnie problemem)


0

Spójrzmy prawdzie w oczy: głównym scenariuszem przypadku użycia jest odczytanie strumienia danych (albo przez strumień wejściowy, albo z JDBC ResultSet) i jest mnóstwo niezliczonych ciągów, które są powtarzane przez cały czas.

Oto mała sztuczka, która daje ci kontrolę nad tym, jakiego rodzaju mechanizmu chcesz użyć do internalizacji łańcuchów i innych niezmiennych, a także przykładową implementację:

/**
 * Extends the notion of String.intern() to different mechanisms and
 * different types. For example, an implementation can use an
 * LRUCache<T,?>, or a WeakHashMap.
 */
public interface Internalizer<T> {
    public T get(T obj);
}
public static class LRUInternalizer<T> implements Internalizer<T> {
    private final LRUCache<T, T> cache;
    public LRUInternalizer(int size) {
        cache = new LRUCache<T, T>(size) {
            private static final long serialVersionUID = 1L;
            @Override
            protected T retrieve(T key) {
                return key;
            }
        };
    }
    @Override
    public T get(T obj) {
        return cache.get(obj);
    }
}
public class PermGenInternalizer implements Internalizer<String> {
    @Override
    public String get(String obj) {
        return obj.intern();
    }
}

Używam tego często, gdy czytam pola ze strumieni lub z zestawów wyników. Uwaga: LRUCacheto prosta pamięć podręczna oparta na LinkedHashMap<K,V>. Automatycznie wywołuje retrieve()metodę podaną przez użytkownika dla wszystkich braków pamięci podręcznej.

Sposobem na użycie tego jest utworzenie go LRUInternalizerprzed odczytem (lub odczytami), użycie go do internalizacji Ciągów i innych małych niezmiennych obiektów, a następnie uwolnienie go. Na przykład:

Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
    s = internalizer.get(s);
    // store s...
}

0

Używam go do buforowania zawartości około 36000 kodów, które prowadzą do powiązanych nazw. Internuję ciągi w pamięci podręcznej, ponieważ wiele kodów wskazuje na ten sam ciąg.

Internując ciągi w mojej pamięci podręcznej, upewniam się, że kody wskazujące ten sam ciąg faktycznie wskazują na tę samą pamięć, oszczędzając w ten sposób miejsce w pamięci RAM.

Gdyby internowane łańcuchy były w rzeczywistości śmieciami, w ogóle by to nie działało. Zasadniczo podważyłoby to cel internowania. Moje nie będą zbierane śmieci, ponieważ przechowuję odwołanie do każdego ciągu w pamięci podręcznej.


Nie, wszystkie internalizowane równe ciągi, które są w pamięci w określonym czasie, nadal będą tym samym obiektem. Będzie to inny obiekt niż ten sam ciąg znaków, który był w pamięci przed jego odebraniem. Ale to nie jest problem, ponieważ starego łańcucha już nie ma.
bdruemen

0

Koszt internalizacji łańcucha jest znacznie większy niż czas zaoszczędzony w porównaniu pojedynczego łańcucha A. Równania (B). Używaj go (ze względu na wydajność) tylko wtedy, gdy wielokrotnie używasz tych samych niezmienionych zmiennych łańcuchowych. Na przykład, jeśli regularnie iterujesz po stabilnej liście ciągów, aby zaktualizować niektóre mapy wpisane w tym samym polu ciągów, możesz uzyskać niezłą oszczędność.

Sugerowałbym zastosowanie internowania ciągów w celu poprawienia wydajności podczas optymalizacji określonych części kodu.

Pamiętaj też, że String są niezmienne i nie popełniają głupiego błędu

String a = SOME_RANDOM_VALUE
a.intern()

pamiętaj, aby zrobić

String a = SOME_RANDOM_VALUE.intern()

0

Jeśli szukasz nielimitowanego zamiennika dla String.intern, a także zbieranych śmieci, poniższe elementy działają dobrze dla mnie.

private static WeakHashMap<String, WeakReference<String>> internStrings = new WeakHashMap<>();
public static String internalize(String k) {
    synchronized (internStrings) {
        WeakReference<String> weakReference = internStrings.get(k);
        String v = weakReference != null ? weakReference.get() : null;
        if (v == null) {
            v = k;
            internStrings.put(v, new WeakReference<String>(v));
        }
        return v;
    }
}

Oczywiście, jeśli potrafisz z grubsza oszacować, ile będzie różnych ciągów, po prostu użyj String.intern () z -XX: StringTableSize = highEnoughValue .


SoftRef uczyniłby więcej sese.
vach

@vach Używając WeakReference (zamiast SoftReference) pamięć jest wcześniej zwalniana, więc inne przydziały mogą iść szybciej. Zależy to od tego, co jeszcze robi aplikacja, każdy z nich może mieć sens.
bdruemen,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.