Хорошо ли использовать java.lang.String.intern()?

Javadoc о String.intern() не дает подробностей. (В двух словах: он возвращает каноническое представление строки, позволяя сравнивать интернированные строки с помощью ==)

  • Когда я буду использовать эту функцию в пользу String.equals()?
  • Есть ли побочные эффекты, не упомянутые в Javadoc, т.е. более или менее оптимизация компилятором JIT?
  • Существуют ли дополнительные возможности String.intern()?

Ответ 1

Когда я буду использовать эту функцию в пользу String.equals()

когда вам нужна скорость, поскольку вы можете сравнивать строки по ссылке (== быстрее, чем равные)

Есть ли побочные эффекты, не упомянутые в Javadoc?

Основной недостаток заключается в том, что вы должны помнить, что вы действительно выполняете intern() все строки, которые вы собираетесь сравнивать. Легко забыть intern() все строки, а затем вы можете получить неверно неверные результаты. Кроме того, для всех, пожалуйста, не забудьте четко указать, что вы полагаетесь на интернализацию строк.

Второй недостаток, если вы решите интернализировать строки, заключается в том, что метод intern() относительно дорог. Он должен управлять пулом уникальных строк, чтобы он выполнял справедливую работу (даже если строка уже была интернализована). Поэтому будьте осторожны в своем коде, чтобы вы, например, intern(), все соответствующие строки вводили, поэтому вам больше не нужно беспокоиться об этом.

(от JGuru)

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

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

Ответ 2

Это (почти) не имеет никакого отношения к сопоставлению строк. String interning предназначен для сохранения памяти, если у вас много строк с одним и тем же контентом в вашем приложении. Используя String.intern(), приложение будет иметь только один экземпляр в конечном счете, а побочным эффектом является то, что вы можете выполнить быстрое сравнение сравнения ссылок вместо обычного сравнения строк (но это обычно нецелесообразно, потому что это очень легко сломать, забыв чтобы ставить только один экземпляр).

Ответ 3

String.intern() - определенно мусор, собранный в современных JVM.
Следующий НИКОГДА не исчерпывает память из-за активности 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().

Ответ 4

Недавно я написал статью о реализации 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 (по умолчанию, вероятно, слишком мало, установите первичный номер)

Ответ 5

Сравнение строк с == намного быстрее, чем с equals()

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

String.intern() вытащить строку из кучи и поместить ее в PermGen

Интернализированные строки помещаются в другую область хранения: Постоянное поколение, которое является областью JVM, зарезервированной для объектов, не являющихся пользователями, например Классы, Методы и другие внутренние объекты JVM. Размер этой области ограничен, и это очень дорого, чем куча. Поскольку эта область меньше, чем куча, существует большая вероятность использовать все пространство и получить исключение OutOfMemoryException.

Строка String.intern() содержит сбор мусора

В новых версиях JVM также интернализованная строка представляет собой сбор мусора, если какой-либо объект не ссылается на него.

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

Ответ 6

Не знаю каких-либо преимуществ, и если бы у кого-то было бы мнение, что equals() сам будет использовать intern() внутри (это не так).

Обманывание мифов() мифов

Ответ 7

Когда я буду использовать эту функцию в пользу String.equals()

Учитывая, что они делают разные вещи, возможно, никогда.

Внутренние строки по соображениям производительности, чтобы вы могли сравнивать их для ссылочного равенства, будут полезны, если вы некоторое время удерживаете ссылки на строки - строки, поступающие с пользовательского ввода или ввода-вывода, не будут интернированы.

Это означает, что в вашем приложении вы получаете вход от внешнего источника и обрабатываете его в объект, который имеет семантическое значение, - говорит идентификатор, - но этот объект имеет тип, неотличимый от необработанных данных, и имеет разные правила относительно того, как программист должен его использовать.

Почти всегда лучше создать тип UserId, который интернирован (легко создать безопасный для потока общий механизм интернирования) и действует как открытое перечисление, чем перегружать тип java.lang.String ссылочной семантикой, если он это идентификатор пользователя.

Таким образом, вы не получаете путаницы между тем, была ли интернированная конкретная String, и вы можете инкапсулировать любое дополнительное поведение, которое требуется в открытое перечисление.

Ответ 8

Есть ли побочные эффекты, не упомянутые в 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; ссылка на этот экземпляр класса является результатом строкового литерала. Наконец, вызывается метод intern нового экземпляра String.

Bytecode

Поучительно также взглянуть на реализацию байт-кода на 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)
  • struct указывает на CONSTANT_Utf8_info Структура, которая содержит данные. Это единственные необходимые данные для представления строки.

и приведенная выше цитата JVMS, похоже, говорит, что всякий раз, когда указатель Utf8 одинаковый, то идентичные экземпляры загружаются ldc.

Я сделал аналогичные тесты для полей и:

  • static final String s = "abc" указывает на таблицу констант через ConstantValue Attribute
  • Не конечные поля не имеют этого атрибута, но все еще могут быть инициализированы с помощью ldc

Бонус: сравните это с Целочисленным пулом, который не имеет прямой поддержки байт-кода (т.е. аналога CONSTANT_String_info).

Ответ 9

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

Ответ 10

Утечка памяти может возникнуть из-за использования subString(), когда результат мал по сравнению с исходной строкой, и объект имеет долгий срок службы.

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

Ответ 11

Интерполяция строк применима в случае, когда метод equals() вызывается часто, потому что метод equals() выполняет быструю проверку, чтобы проверить, совпадают ли объекты в начале метода.

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

Обычно это происходит при поиске через Collection, хотя другой код также может выполнять проверки равенства строк.

Есть затраты, связанные с интернированием, хотя я выполнил микрообнаружение некоторого кода и обнаружил, что процесс интернирования увеличивает время выполнения в 10 раз.

Лучшее место для интернирования обычно происходит, когда вы читаете ключи, которые хранятся вне кода, поскольку строки в коде автоматически интернированы. Это обычно происходит на этапах инициализации вашего приложения, чтобы предотвратить штраф первого пользователя.

Другое место, где это можно сделать, - это обработка пользовательского ввода, который можно использовать для выполнения ключевых поисков. Это обычно происходит в вашем процессоре запросов, обратите внимание, что интернированные строки должны быть переданы вниз.

Кроме того, в остальной части кода не так много делается, как обычно, это не принесет никакой пользы.

Ответ 12

Я бы проголосовал за то, чтобы это не стоило хлопот обслуживания.

В большинстве случаев не будет никакой необходимости и нет выгоды от производительности, если вы не используете код с большой подстрокой. В этом случае класс String будет использовать исходную строку плюс смещение для сохранения памяти. Если ваш код использует подстроки много, то я подозреваю, что это просто вызовет взрыв памяти.

Ответ 13

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 для String, поэтому тест почти всегда передается. Действительно, приоритет "==" внутри String.equals() подразумевает уверенность в том, что вы часто сравниваете String с одним и тем же фактическим объектом.

Надеюсь, никто не удивится, что следующие строки производят результат "false":

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

Но если вы измените i на i.toString() во второй строке, конечно, true.

Места, где вы, возможно, надеетесь получить выгоду от интернирования, включают Set и Map, очевидно. Надеюсь, что интернированные строки имеют хэш-коды, кэшированные... Я думаю, это было бы требованием. И я надеюсь, что я не просто отдал идею, которая могла бы заработать мне миллион долларов.: -)

Что касается памяти, то также очевидно, что это важный предел, если ваш объем строк большой, или если вы хотите, чтобы память, используемая вашим программным кодом, была очень маленькой. Если ваш объем -distinct-Strings очень велик, возможно, пришло время рассмотреть использование специального кода программы базы данных для их управления и отдельного сервера базы данных. Аналогичным образом, если вы можете улучшить небольшую программу (которая должна запускаться в 10000 экземплярах одновременно), если она вообще не хранит свои строки.

Мне кажется, расточительно создавать новую строку, а затем сразу же отбросить ее для своей замены intern(), но нет ясной альтернативы, за исключением сохранения дублирующей строки. Так что действительно стоимость исполнения - это поиск вашей строки в пуле пользователя, а затем разрешение сборщику мусора избавиться от оригинала. И если это строковый литерал, то он все равно идет по-прежнему.

Мне интересно, можно ли злоупотреблять intern() вредоносным программным кодом, чтобы определить, существуют ли уже некоторые String и их объектные ссылки в пуле intern() и, следовательно, существуют в другом месте сеанса Java, когда это не должно быть известен. Но это возможно только тогда, когда программный код уже используется доверенным способом, я думаю. Тем не менее, это то, что нужно учитывать о сторонних библиотеках, которые вы включаете в свою программу для хранения и запоминания ваших PIN-кодов ATM!

Ответ 14

Даниэль Брюкнер абсолютно прав. Интерпретация строк предназначена для сохранения памяти (кучи). Наша система в настоящее время имеет гигантский хэш файл для хранения определенных данных. В качестве системных шкал хэш-карта будет достаточно большой, чтобы вывести кучу из памяти (как мы протестировали). Путем интернирования всех дублированных строк все объекты в hashmap, это экономит нам значительное количество кучи.

Также в Java 7 интернированные строки уже давно не живут в Пермигене, но вместо кучи. Так что вам не нужно беспокоиться о его размере и да, он получает сбор мусора:

В JDK 7 интернированные строки больше не выделяются в постоянных генерации кучи Java, но вместо этого распределяются в основном часть кучи Java (известная как молодое и старое поколение), вдоль с другими объектами, созданными приложением. Это изменение будет приводят к большему количеству данных, находящихся в основной куче Java, и меньше данных в постоянное поколение, и, следовательно, может потребоваться размер кучи отрегулированы. Большинство приложений будут видеть только относительно небольшие различия в использовании кучи из-за этого изменения, но более крупные приложения, которые загружают многие классы или сильно используют метод String.intern() более значительные различия.

Ответ 15

Настоящая причина использования стажера не выше. Вы можете использовать его после того, как получите ошибку из памяти. Множество строк в типичной программе - String.substring() другой большой строки [подумайте о том, чтобы вытащить имя пользователя из файла 100K xml. Реализация java заключается в том, что подстрока содержит ссылку на исходную строку и начало + конец этой огромной строки. (Мысль за ним - повторное использование одной и той же большой строки)

После 1000 больших файлов, из которых вы сохраняете только 1000 коротких имен, вы будете хранить в памяти все 1000 файлов! Решение: в этом сценарии просто используйте smallsubstring.intern()

Ответ 16

Я использую intern для сохранения памяти, я храню большое количество данных String в памяти и перейдя к использованию intern(), сохранил массивный объем памяти. К сожалению, хотя он использует гораздо меньше памяти, память, которую он использует, хранится в памяти PermGen, а не в куче, и трудно объяснить клиентам, как увеличить выделение этого типа памяти.

Итак, есть ли альтернатива intern() для сокращения потребления памяти, (выгоды от сравнения с равными равными для меня не являются).

Ответ 17

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

Вот небольшой трюк, который дает вам некоторый контроль над тем, какой механизм вы хотите использовать для интернализации строк и других неизменяемых объектов и пример реализации:

/**
 * 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...
}

Ответ 18

Я использую его для кэширования содержимого примерно 36000 кодов, которые ссылаются на связанные имена. Я старую строки в кеше, потому что многие из кодов указывают на одну и ту же строку.

Интерпретируя строки в моем кеше, я уверен, что коды, указывающие на одну и ту же строку, фактически указывают на одну и ту же память, тем самым сохраняя пространство памяти.

Если бы интернированные струны были фактически собраны мусором, это не сработало бы для меня вообще. Это в основном отрицает цель интернирования. Mine не будет собирать мусор, потому что я держу ссылку на каждую строку в кеше.

Ответ 19

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

Я бы предложил использовать интернирование строк, чтобы настроить производительность, когда вы оптимизируете определенные части вашего кода.

Также помните, что String неизменяемы и не делают глупую ошибку

String a = SOME_RANDOM_VALUE
a.intern()

не забудьте сделать

String a = SOME_RANDOM_VALUE.intern()

Ответ 20

Если вы ищете неограниченную замену 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.