Чи корисно використовувати java.lang.String.intern ()?


194

Javadoc про String.intern()не дає багато деталей. (Коротше кажучи: він повертає канонічне зображення рядка, що дозволяє порівняти інтерновані рядки за допомогою ==)

  • Коли я використовую цю функцію на користь String.equals()?
  • Чи є побічні ефекти, про які не згадується в Javadoc, тобто більш-менш оптимізація компілятором JIT?
  • Чи є додаткові можливості використання String.intern()?

14
Виклик стажера () має власний вплив на виконання, використовуючи intern () для поліпшення продуктивності, потрібно перевірити, щоб переконатися, що він дійсно пришвидшить вашу програму, щоб бути вартим додаткової складності. Ви також можете використовувати це для зменшення споживання пам’яті для великих таблиць із відносно повторюваними значеннями. Однак в обох випадках є й інші варіанти, які можуть бути кращими.
Пітер Лорі

Так, стажер () має власний вплив на ефективність роботи. Тим більше, що вартість intern () збільшується лінійно, коли ви стажуєте струни та зберігаєте посилання на них. Принаймні, на сонце / оракул 1.6.0_30 вм.
lacroix1547

Відповіді:


125

Коли я використовую цю функцію на користь String.equals ()

коли вам потрібна швидкість, оскільки ви можете порівнювати рядки за посиланням (== швидше, ніж рівне)

Чи є побічні ефекти, про які не згадується в Javadoc?

Основним недоліком є ​​те, що вам потрібно пам’ятати, щоб переконатися, що ви насправді стажуєте () всі рядки, з якими збираєтесь порівнювати. Легко забути інтернувати () всі рядки, і тоді ви можете отримати заплутано неправильні результати. Також, ради кожного, будь ласка, обов'язково дуже чітко задокументуйте, що ви покладаєтесь на інтерналізовані рядки.

Другий недолік, якщо ви вирішили інтерналізувати рядки, - це те, що метод intern () є відносно дорогим. Він повинен керувати пулом унікальних рядків, щоб це зробило неабияку роботу (навіть якщо рядок уже інтерналізовано). Отже, будьте обережні в розробці коду, щоб ви, наприклад, інтернували () всі відповідні рядки на вході, щоб вам більше не потрібно було турбуватися про це.

(від JGuru)

Третій недолік (лише Java 7 або менше): інтерновані струни живуть у просторі PermGen, якого зазвичай досить мало; ви можете зіткнутися з OutOfMemoryError з великою кількістю вільного місця.

(від Майкла Боргвардта)


64
Третій недолік: інтерновані струни живуть у просторі PermGen, якого зазвичай досить мало; Ви можете зіткнутися з OutOfMemoryError з великою кількістю вільного місця.
Майкл Боргвардт

15
Нові автомашини AFAIK також сміття збирають PermGen простір.
Даніель Ріковський

31
Стажер - це управління пам'яттю, а не швидкість порівняння. Різниця між if (s1.equals(s2))і if (i1 == i2)мінімальна, якщо у вас багато довгих рядків з однаковими провідними символами. У більшості реальних застосувань (крім URL-адрес) рядки будуть відрізнятися протягом перших кількох символів. І довгі ланцюги "if-else" все одно є кодовим запахом: використовуйте переписки та карти функторів.
kdgregory

25
ви все ще можете використовувати синтаксис s1.equals у всій програмі, DONT використовувати ==, .equals use == внутрішньо для оцінки короткого замикання
gtrak

15
Майкл Боргвардт НЕ сказав, що інтерновані струни не можна збирати сміттям. І це хибне твердження. Те, що кажуть у коментарях Майкла (правильно), є більш тонким.
Стівен C

193

Це (майже) не має нічого спільного з порівнянням рядків. Рядок інтернування призначений для економії пам'яті, якщо у вашій програмі є багато рядків з однаковим вмістом. Використовуючи String.intern()додаток, буде мати лише один екземпляр у довгостроковій перспективі, і побічним ефектом є те, що ви можете виконати швидке порівняння рівності посилань замість звичайного порівняння рядків (але це, як правило, недоцільно, тому що його дуже легко перервати, забувши лише пройти стажування єдиний екземпляр).


4
Це не правильно. Інтернування рядків відбувається завжди, автоматично, коли оцінюється кожне вираження рядка. Завжди є одна копія для кожного унікального ряду символів, який використовується, і це "внутрішньо спільне використання", якщо трапляється кілька звичаїв. Виклик String.intern () не робить цього все - він просто повертає внутрішнє канонічне подання. Дивіться javadoc.
Glen Best

16
Необхідно уточнити - інтернування завжди відбувається автоматично для постійних рядків часу компіляції (літерали та фіксовані вирази). Крім того, це відбувається, коли String.intern () викликається в динамічно оцінюваних рядках для виконання часу.
Glen Best

Отже, ви маєте на увазі, якщо в Heap є 1000 об'єктів "Hello" і я виконую інтерн () на одному з них, то решта 999 об'єктів будуть знищені автоматично?
Арун Раай

@ArunRaaj ні, у вас буде ще 1000 в купі, а додаткова - у пулі для стажування, яка може бути готова до повторного використання пізніше, str.intern()коли strє "Hello".
Матьє

37

String.intern()це, безумовно, сміття, зібране в сучасних СП.
Наступні НІКОЛИ не втрачають пам’яті через активність 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();
        }
    }
}

Дивіться більше (від мене) про міф про не GCed String.intern () .


26
OutOfMemoryException- ні, не код вище, в моєму мозку : посилання на статтю javaturning, яка вказує на цю статтю, яка вказує на статтю javaturning, яка ... :-)
user85421

Хоча ви бачите, що публікація була відредагована, щоб додати це посилання;)
Riking

3
Ви можете згадати, що ви є автором зовнішньої посилання, на яку ви посилаєтесь.
Thorbjørn Ravn Andersen

11
@Carlos, що посилається на зовнішнє посилання, яке посилається на stackoverflow, повинно спричинити .. Stackoverflow :)
Seiti

2
Циклічні посилання @Seiti легко виявляються в наші дні: p
Ajay

16

Нещодавно я написав статтю про реалізацію String.intern () в Java 6, 7 і 8: String.intern на Java 6, 7 і 8 - об'єднання рядків .

Я сподіваюся, що він повинен містити достатньо інформації про поточну ситуацію з об'єднанням рядків на Java.

Коротко:

  • Уникайте String.intern()в Java 6, тому що вона переходить у PermGen
  • Віддайте перевагу String.intern()в Java 7 та Java 8: вона використовує на 4-5 разів менше пам’яті, ніж прокатка власного пулу об’єктів
  • Обов'язково налаштуйте -XX:StringTableSize(за замовчуванням, мабуть, занадто мало; встановіть основний номер)

3
Будь ласка, не публікуйте посилання на ваш блог, деякі вважають це СПАМ. Плюс посилання на блог мають помітну тенденцію до смерті 404 років. Будь ласка, або узагальнюйте тут свою статтю, або залиште це посилання в коментарі до питання.
Мат

3
Дякуємо, що написали це @ mik1! Дуже інформативна, чітка та актуальна стаття. (Я повернувся сюди, маючи намір сам опублікувати посилання.)
Люк Ушервуд

1
Дякуємо, що згадали про аргумент -XX. Ви також можете скористатися цим, щоб побачити статистику таблиці: -XX: + PrintStringTableStatistics
csadler

13

Порівняння рядків з == набагато швидше, ніж з рівними ()

5 Час швидше, але оскільки порівняння рядків зазвичай становить лише невеликий відсоток від загального часу виконання програми, загальний коефіцієнт посилення набагато менший, ніж кінцевий приріст буде розведений до кількох відсотків.

String.intern () відтягніть рядок від Heap і покладіть її в PermGen

Інтерналізовані рядки розміщуються в іншій зоні зберігання: Постійне покоління - це область JVM, яка зарезервована для об'єктів, які не користуються користувачем, як Класи, Методи та інші внутрішні об'єкти JVM. Розмір цієї площі обмежений, і це набагато дорого, ніж купа. Оскільки ця область менша, ніж Heap, є більша ймовірність використовувати весь простір та отримати OutOfMemoryException.

String.intern () рядок збирають сміття

У нових версіях JVM також інтерналізована рядок - це сміття, яке збирається, коли жоден об'єкт не посилається на нього.

Маючи на увазі вищенаведений 3 пункт, ви могли б вирахувати, що String intern () може бути корисним лише в кількох ситуаціях, коли ви робите багато порівняння рядків, проте краще не використовувати внутрішній рядок, якщо ви точно не знаєте, що ви роблять ...



1
Додамо, винятки з пам'яті Heap іноді можна відновити, особливо в потокових моделях, таких як веб-програми. Коли пермген вичерпаний, додаток, як правило, буде постійно не функціональним і часто буде ресурсом треш, поки не буде знищено.
Тейлор

7

Коли я використовую цю функцію на користь String.equals ()

Враховуючи, що вони роблять різні речі, можливо, ніколи.

Інтернування рядків з міркувань продуктивності, так що ви можете порівняти їх для рівності еталонів, буде корисним лише у тому випадку, якщо ви деякий час утримуєте посилання на рядки - рядки, що надходять із введення користувача, або IO не будуть інтерновані.

Це означає, що у вашій програмі ви отримуєте вхід із зовнішнього джерела і обробляєте його в об'єкт, який має семантичне значення - ідентифікатор кажуть - але цей об’єкт має тип, який не можна відрізнити від необроблених даних, і має різні правила щодо того, як повинен програміст використай це.

Майже завжди краще створити UserIdтип інтернованих (легко створити безпечний для потоків загальний механізм інтернування) і діяти як відкрита перерахунок, ніж перевантажувати java.lang.Stringтип референтною семантикою, якщо це трапляється як User ID.

Таким чином, у вас не виникає плутанини між тим, чи є інтернація певної струни чи ні, і ви можете інкапсулювати будь-яку додаткову поведінку, яка вам потрібна, у відкритому перерахунку.


6

Я не знаю жодних переваг, і якби вони були, я б подумав, що equals () сам би використовував intern () внутрішньо (чого він не робить).

Міфи про інтерни ()


7
Незважаючи на те, що ви сказали, що не знаєте жодних переваг, ваш розміщений зв’язок визначає порівняння через == як 5 разів швидше і, таким чином, важливе для тексту, орієнтованого на текст
Brian Agnew

3
Коли у вас буде багато текстового порівняння, з часом вичерпаєтеся простору PermGen. Коли для порівняння тексту не так багато, різниця в швидкості не має значення. Так чи інакше, просто не інтернуйтеся () своїми струнами. Це не варто.
Бомбе

Крім того, йдеться про те, що загальний відносний приріст, як правило, буде невеликим.
об’єкти

Я не думаю, що така логіка є дійсною. Хороша посилання, хоча!
Даніель Ріковський

1
@DR: яка логіка? Це одна велика помилка. @objects: вибачте, але ваші аргументи не відповідають причинам. Є дуже вагомі причини використання internта дуже вагомі причини, які equalsне роблять цього за замовчуванням. Посилання, яке ви опублікували, - це повний облік Останній абзац навіть визнає, що internмає дійсний сценарій використання: важка обробка тексту (наприклад, аналізатор). Висновок, що "[XYZ] небезпечний, якщо ти не знаєш, що ти робиш", настільки банальний, що фізично боляче.
Конрад Рудольф

4

Даніель Брюкнер абсолютно правий. Струнне інтернування призначене для економії пам'яті (купи). Зараз у нашій системі є гігантська хеш-карта для зберігання певних даних. Як масштаби системи, хешмап буде досить великим, щоб звільнити купу пам'яті (як ми тестували). Інтернувавши всі дублюються рядки всіх об'єктів у хешмапі, це економить нам значну кількість купольного простору.

Також у Java 7 інтерновані рядки вже не живуть у PermGen, а купуються. Тож вам не потрібно турбуватися про його розмір, і так, це збирає сміття:

У JDK 7 інтерновані рядки вже не виділяються в постійне покоління кучі Java, а замість цього виділяються в основній частині кучі Java (відомі як молоді та старі покоління) разом з іншими об'єктами, створеними додатком . Ця зміна призведе до збільшення кількості даних, що зберігаються в основній купі Java, і меншої кількості даних у постійному поколінні, і, таким чином, може знадобитися коригування розмірів купи. Більшість додатків побачать лише відносно невеликі відмінності у використанні купи через цю зміну, але більші додатки, які завантажують багато класів або широко використовують метод String.intern (), побачать більш значні відмінності.


Я маю зауважити, що: на моєму програмному забезпеченні купи звалища показали, що більшість купольного простору використовується Stringекземплярами. Переглянувши їхній вміст, я побачив багато дублікатів і вирішив перейти до intern(), що заощадило сотні МБ.
Матьє

4

Чи є побічні ефекти, про які не згадується в Javadoc, тобто більш-менш оптимізація компілятором JIT?

Я не знаю про рівень JIT, але є прямий байт-код підтримки для рядкового пулу , який реалізується магічно та ефективно з виділеною CONSTANT_String_infoструктурою (на відміну від більшості інших об'єктів, які мають більш загальні уявлення).

JVMS

JVMS 7 5.1 говорить :

Строковий літерал - це посилання на екземпляр класу String і походить від CONSTANT_String_info структури (§4.4.3) у двійковому поданні класу чи інтерфейсу. Структура CONSTANT_String_info дає послідовність точок коду Unicode, що складають рядковий літерал.

Мова програмування Java вимагає, щоб однакові рядкові літерали (тобто літерали, що містять однакову послідовність точок коду), повинні посилатися на той самий екземпляр класу String (JLS §3.10.5). Крім того, якщо метод String.intern викликається в будь-якому рядку, результат є посиланням на той самий екземпляр класу, який буде повернуто, якби ця рядок виявилася як буквальна. Таким чином, наступний вираз повинен мати значення true:

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

Щоб отримати рядковий літерал, віртуальна машина Java вивчає послідовність кодів, заданих структурою CONSTANT_String_info.

  • Якщо метод String.intern раніше був викликаний на екземпляр класу String, що містить послідовність точок коду Unicode, ідентичну тій, що задана структурою CONSTANT_String_info, то результатом виведення рядкового літералу є посилання на той самий екземпляр класу String.

  • В іншому випадку створюється новий екземпляр класу String, що містить послідовність точок коду Unicode, заданих структурою CONSTANT_String_info; посилання на цей екземпляр класу є результатом рядкового буквеного виведення. Нарешті, використовується інтерн-метод нового екземпляра String.

Байт-код

Також корисно подивитися на реалізацію байт-коду на OpenJDK 7.

Якщо ми декомпілюємо:

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

у нас на постійному басейні:

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

і 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

Зверніть увагу, як:

  • 0і 3: однакова ldc #2константа завантажена (буквали)
  • 12: створюється новий екземпляр рядка (з #2аргументом)
  • 35: aі cпорівнюються із звичайними об'єктамиif_acmpne

Представлення постійних рядків є досить магічним у байт-коді:

  • вона має виділену CONSTANT_String_info структуру, на відміну від звичайних об'єктів (наприклад new String)
  • структура вказує на структуру CONSTANT_Utf8_info, яка містить дані. Це єдині необхідні дані для представлення рядка.

а цитата JVMS вище, схоже, говорить про те, що коли вказівка ​​Utf8 є однаковою, то ідентичні екземпляри завантажуються ldc .

Я зробив подібні тести для полів, і:

  • static final String s = "abc"вказує на постійну таблицю через атрибут ConstantValue
  • не завершальні поля не мають цього атрибуту, але все ще можуть бути ініціалізовані ldc

Бонус : порівняйте це з пулом Integer , який не підтримує прямий байт-код (тобто немає CONSTANT_String_infoаналога).


2

Я розглядаю інтерн і == - порівняння замість рівного лише у випадку, коли рівне порівняння є вузьким місцем у кількох порівняннях рядків. Це навряд чи допоможе у порівнянні з невеликою кількістю порівнянь, оскільки стажер () не є безкоштовним. Після агресивного інтернування рядків ви виявите, що дзвінки до інтерну () стають все повільнішими та повільнішими.


2

Різний витік пам’яті може виникнути внаслідок використання, subString()коли результат невеликий порівняно з вихідним рядком і об’єкт має тривалий термін служби.

Нормальним рішенням є використання, new String( s.subString(...))але коли у вас є клас, який зберігає результат потенційного / ймовірно subString(...)і не має контролю над абонентом, ви можете розглянути можливість зберігання intern()аргументів String, переданих конструктору. Це звільняє потенційний великий буфер.


Цікаво, але, можливо, це залежить від реалізації.
акостадінов

1
Вищезгадане потенційне витоку пам’яті не відбувається в java 1.8 та 1.7.06 (і новіших версіях), див. Зміни в внутрішньому представництві String, зробленому на Java 1.7.0_06 .
ереммель

що підтверджує мікрооптимізацію, слід застосовувати лише в разі необхідності після профілювання продуктивності та / або пам'яті. Дякую.
акостадінов

2

Рядок інтернування корисний у тому випадку, коли equals()метод часто викликається, оскільки equals()метод робить швидку перевірку, чи є об'єкти однаковими на початку методу.

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

Зазвичай це відбувається під час пошуку через Collectionхоч інший код також може робити перевірку рівності рядків.

Хоча для стажування є вартість, проте я здійснив мікро-показник деякого коду і виявив, що процес інтернування збільшує час виконання в 10 разів.

Найкраще місце для інтернування - зазвичай, коли ви читаєте ключі, які зберігаються поза кодом, оскільки рядки в коді автоматично інтернуються. Зазвичай це відбувається на етапах ініціалізації вашої заявки, щоб запобігти штрафу першого користувача.

Ще одне місце, де це можна зробити, - це при обробці вводу користувача, який може бути використаний для пошуку ключів. Зазвичай це відбувається у вашому процесорі запитів, зауважте, що інтерновані рядки повинні бути передані вниз.

Крім цього, не дуже багато сенсу робити інтернування в решті коду, оскільки це, як правило, не дасть ніякої користі.


1

Я б проголосував за те, щоб це не вартувало клопотів з технічного обслуговування.

Більшу частину часу не буде потреби та користі від продуктивності, якщо ви не будете багато працювати з підрядками. У цьому випадку клас String використовує оригінальну рядок плюс зміщення для збереження пам'яті. Якщо у вашому коді багато використовується підрядків, я підозрюю, що це просто спричинить вибух вашої пам'яті.


1

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

стверджує, що String.equals()використовує "=="для порівняння Stringоб'єктів раніше, відповідно до

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

він порівнює довжини рядків, а потім вміст.

(До речі, рядки коду товару в каталозі продажів можуть бути однакової довжини - BIC0417 - шолом безпеки велосипедиста, TIG0003 - тигр для дорослих чоловіків - вам, мабуть, потрібні всілякі ліцензії, щоб замовити одну з них. І можливо, вам краще замовити шолом безпеки одночасно.)

Таким чином, це здається, ніби ви отримуєте користь від заміни своїх рядків на їх intern()версію, але ви отримуєте безпеку - і читабельність та стандартну відповідність - -без- використовуючи "==" для equals()свого програмування. І більшість того, що я збираюся сказати, залежить від того, що це правда, якщо це правда.

Але чи String.equals()перевіряє те, що ти передав йому рядок, а не якийсь інший об'єкт, перш ніж використовувати "=="? Я не кваліфікований, щоб сказати, але гадаю, що ні, оскільки переважна більшість таких equals()операцій буде "String to String", тому цей тест майже завжди здається. Дійсно, пріоритетність "==" всередині String.equals()передбачає впевненість, що ви часто порівнюєте String з тим самим фактичним об'єктом.

Я сподіваюся, що ніхто не здивований, що наступні рядки дають результат "помилкового":

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

Але якщо ви зміните iдо i.toString()у другому рядку, звичайно , це true.

Місця, де ви можете сподіватися на користь від стажування, включають Setі Map, очевидно. Я сподіваюсь, що в інтернованих рядках є свої хеш-коди ... Я думаю, це було б вимогою. І я сподіваюся, що я не просто дав ідею, яка могла б заробити мені мільйон доларів. :-)

Що стосується пам'яті, то також очевидно, що це важлива межа, якщо обсяг Strings великий, або якщо ви хочете, щоб пам'ять, використовувана вашим програмним кодом, була дуже маленькою. Якщо ваш об'єм -distinct- Strings дуже великий, можливо, настав час розглянути можливість використання спеціального коду програми для управління ними та окремого сервера баз даних. Так само, якщо ви можете вдосконалити невелику програму (яку потрібно запускати 10000 екземплярів одночасно), не дозволяючи їй взагалі зберігати свої Strings.

Створювати нову струну, а потім відкидати її відразу, замість неї intern()марно, але немає чіткої альтернативи, окрім збереження дубліката String. Тож дійсно вартість виконання полягає в пошуку вашого рядка в басейні стажування, а потім дозволяється сміттєзбірнику утилізувати оригінал. І якщо це рядковий літерал, то він все-таки приходить на практику.

Мені цікаво, чи intern()можна зловживати шкідливим програмним кодом, щоб виявити, чи існують деякі об'єкти String та їх об'єкти в intern()пулі, а отже, існують і в іншому місці сеансу Java, коли це не повинно бути відомо. Але це було б можливо лише тоді, коли програмний код уже використовується довірливим способом. І все-таки варто враховувати сторонні бібліотеки, які ви включаєте у свою програму, щоб зберігати та запам'ятовувати свої PIN-номери банкоматів!


0

Справжня причина використання інтерна - це не вище. Ви можете користуватися ним після помилки з пам'яттю. Багато рядків у типовій програмі - це String.substring () іншої великої струни [подумайте про вилучення імені користувача з файлу розміром 100 К xml. Реалізація Java полягає в тому, що в підрядці міститься посилання на початковий рядок і початок + кінець цієї величезної рядки. (Думка за ним - повторне використання тієї ж великої струни)

Після 1000 великих файлів, з яких ви зберігаєте лише 1000 коротких імен, ви збережете в пам'яті цілих 1000 файлів! Рішення: у цьому сценарії просто використовуйте smallsubstring.intern ()


Чому б просто не створити нову рядок із підрядки, якщо вона потрібна?
Thorbjørn Ravn Andersen

0

Я використовую інтерн для збереження пам’яті, я зберігаю велику кількість даних String в пам’яті і переходячи до використання intern (), зберегла величезну кількість пам’яті. На жаль, хоча він використовує набагато менше пам'яті, пам'ять, яку він використовує, зберігається в пам’яті PermGen, а не в Heap, і важко пояснити клієнтам, як збільшити розподіл цього типу пам’яті.

Тож чи існує альтернатива intern () для зменшення споживання пам’яті, (== порівняно з рівними перевагами для продуктивності не є для мене)


0

Давайте визначимось: основний сценарій використання - це коли ви читаєте потік даних (або через вхідний потік, або з JDBC ResultSet), і є безліч маленьких рядків, які повторюються протягом усього часу.

Ось невеликий трюк, який дає вам певний контроль над тим, який механізм ви хочете використовувати для інтерналізації Strings та інших незмінних компонентів, та приклад реалізації:

/**
 * 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();
    }
}

Це я часто використовую, коли читаю поля з потоків або з ResultSets. Примітка: LRUCacheце простий кеш на основі LinkedHashMap<K,V>. Він автоматично викликає наданий користувачем retrieve()метод для всіх пропусків кешу.

Спосіб використання цього - створити його LRUInternalizerперед читанням (чи читанням), використовувати його для інтерналізації рядків та інших невеликих незмінних об'єктів, а потім звільнити його. Наприклад:

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

Я використовую його для кешування вмісту приблизно 36000 кодів, які посилаються на пов'язані імена. Я інтерную струни в кеші, оскільки багато кодів вказують на один і той же рядок.

Інтернуючи рядки в кеш-пам’яті, я гарантую, що коди, які вказують на ту саму рядок, насправді вказують на ту саму пам’ять, тим самим економлять мені простір оперативної пам’яті.

Якби інтерновані струни насправді збирали сміття, це для мене взагалі не працювало б. Це в основному заперечує мету стажування. У мене не буде зібрано сміття, оскільки я зберігаю посилання на кожну рядок у кеші.


Ні, всі інтерновані рівні рядки, що знаходяться в пам'яті в певний час, все одно будуть однаковим одним об’єктом. Це буде інший об'єкт, ніж рівний рядок, який був у пам’яті до того, як було зібрано сміття. Але це не проблема, тому що старої струни вже немає.
bdruemen

0

Вартість інтернування рядка набагато більше, ніж часу, збереженого в одній порівнянні stringA.equals (B). Використовуйте його (з міркувань продуктивності) лише тоді, коли ви неодноразово використовуєте одні й ті ж незмінені рядкові змінні. Наприклад, якщо ви регулярно повторюєте стабільний список рядків, щоб оновити деякі карти, введені в одне і те ж поле рядка, ви можете отримати хорошу економію.

Я б запропонував використовувати строкове інтернування для налаштування продуктивності під час оптимізації конкретних частин коду.

Також пам’ятайте, що String незмінні і не роблять дурних помилок

String a = SOME_RANDOM_VALUE
a.intern()

пам'ятати робити

String a = SOME_RANDOM_VALUE.intern()

0

Якщо ви шукаєте необмежену заміну для String.intern, також зібраного сміття, наступне добре працює для мене.

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

Звичайно, якщо ви можете приблизно оцінити кількість різних рядків, просто використовуйте String.intern () з -XX: StringTableSize = highEnoughValue .


SoftRef зробить більше сенсу.
vach

@vach За допомогою WeakReference (замість SoftReference) пам'ять звільняється раніше, тому інші розподіли можуть йти швидше. Це залежить від того, що ще робить додаток, чи можна було б мати сенс.
bdruemen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.