Это хорошая практика, чтобы использовать java.lang.String.intern()?

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

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

20 ответов

Решение

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

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

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

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

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

(от Дж.Гуру)

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

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

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

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

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

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

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

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

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

Строка String.intern() - сборщик мусора

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

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

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

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

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

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

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

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

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

Разорение интерна () мифы

Есть ли побочные эффекты, не упомянутые в Javadoc, то есть более или менее оптимизация JIT-компилятором?

Я не знаю об уровне JIT, но есть прямая поддержка байт-кода для пула строк, которая волшебно и эффективно реализована с выделенным CONSTANT_String_info struct (в отличие от большинства других объектов, которые имеют более общие представления).

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)
  • структура указывает на структуру CONSTANT_Utf8_info, которая содержит данные. Это единственные необходимые данные для представления строки.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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() подразумевает уверенность в том, что вы часто сравниваете строку с одним и тем же реальным объектом.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

String a = SOME_RANDOM_VALUE
a.intern()

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

String a = SOME_RANDOM_VALUE.intern()

Посмотрим правде в глаза: основной сценарий использования - это когда вы читаете поток данных (либо через входной поток, либо из 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...
}

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

Другие вопросы по тегам