Когда нам следует использовать метод intern строки String для литералов String


187

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

Итак, я попробовал это:

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

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

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

Я ожидал, что s1 and s3 are sameэто будет напечатано, поскольку s3 интернирован, и s1 and s2 are sameне будет напечатан. Но результат: обе строки напечатаны. Это означает, что по умолчанию строковые константы интернированы. Но если это так, то зачем нам internметод? Другими словами, когда мы должны использовать этот метод?


14
Javadoc, который вы связали, также гласит: «Все литеральные строки и строковые константные выражения интернированы».
Jorn


1
не точный дубликат ..
Божо

1
@Jorn: это верно. Так почему же у нас есть internпубличный метод. Разве у нас не должно быть такого internзакрытого метода, чтобы никто не мог получить к нему доступ. Или есть ли цель этого метода?
Ракеш Джуал

2
@RakeshJuyal: метод intern определен для строкового типа, который может быть строковым литералом или переменной. Как бы вы интернировали переменную, если метод был закрытым?
bobbyalex

Ответы:


230

Java автоматически интернирует строковые литералы. Это означает, что во многих случаях оператор == работает для строк так же, как и для целочисленных значений или других примитивных значений.

Поскольку интернирование выполняется для строковых литералов автоматически, intern()метод должен использоваться в строках, созданных сnew String()

Используя ваш пример:

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

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

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

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

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

вернется:

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

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

Обратитесь к JavaTechniques "Строковое Равенство и Стажировка" для получения дополнительной информации.


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

Новичок в Java (я из мира C # .NET), и я иногда вижу в унаследованном проекте Java "" .intern (), поэтому, если я правильно понимаю, что это "бессмыслица" также для пустых строк.
hfrmobile

4
@Miguel Хорошее объяснение, мой вопрос в том, как объект может быть создан здесь в вашем примере. Вот мое предположение: String s1 = "Rakesh"; первый OB1, String s4 = new String("Rakesh");второй OB2, поэтому остальные (s2, s3, s5) ссылаются на тот же объект (OB1), созданный в «пуле строк». Так что я могу сказать, что .intern()метод, используемый для предотвращения создания нового объекта, если такая же строка доступна в string poolIf мое предположение неверно, поэтому дайте мне направление.
HybrisHelp

1
Ссылка на JavaTechniques не работает
SJuan76


20

В недавнем проекте, некоторые огромные структуры данных были созданы с данными, которые считывались из базы данных (и, следовательно, не константами / литералами String), но с огромным количеством дублирования. Это было банковское приложение, и повсюду появлялись такие вещи, как названия скромных (возможно, 100 или 200) корпораций. Структуры данных уже были большими, и если бы все эти имена корпусов были уникальными объектами, они бы переполнили память. Вместо этого все структуры данных имели ссылки на одни и те же 100 или 200 объектов String, что позволило сэкономить много места.

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

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


@ The downside is that interning a String takes more time than simply throwing it on the heap, and that the space for interned Strings may be limitedдаже если вы не используете метод intern для константы String, он будет автоматически интернирован.
Ракеш Джуал

2
@Rakesh: Обычно в любом конкретном классе не так много String-констант, так что это не проблема пространства / времени с константами.
Дэвид Родригес - dribeas

Да, комментарий Ракеша не применяется, потому что интернирование Strings выполняется только (явно) со строками, которые каким-то образом «генерируются», будь то внутренними манипуляциями или извлечением из базы данных или тому подобное. С константами у нас нет выбора.
Карл Смотриц

2
+1. Я думаю, что это хороший пример того, когда стажировка имеет смысл. Я не согласен на ==строки, хотя.
Александр Погребняк

1
Начиная с Java 7 «Строковый пул» внедряется в пространство кучи, поэтому он получает все преимущества хранения интернов, сбора мусора и его размер не ограничен, его можно увеличить до размера кучи (вам никогда не понадобится так много память на струны)
Анил Уттани

15

Я хочу добавить свои 2 цента при использовании ==с интернированными строками.

Первое, что String.equalsделает this==object.

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

Поэтому я предлагаю не полагаться на особый случай ==интернированных строк, но всегда использовать его так, equalsкак задумал Гослинг.

РЕДАКТИРОВАТЬ: интернированный становится не интернированным:

V1.0
public class MyClass
{
  private String reference_val;

  ...

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

    return false;
  }

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

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

В версии 2.0 сопровождающий решил hasReferenceValобнародовать, не вдаваясь в подробности, что он ожидает массив интернированных строк.

V2.0
public class MyClass
{
  private String reference_val;

  ...

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

    return false;
  }

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

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

Теперь у вас есть ошибка, которую может быть очень трудно найти, потому что в большинстве случаев массив содержит литеральные значения, а иногда используется не-литеральная строка. Если бы equalsиспользовались вместо того, ==то hasReferenceValвсе равно продолжали бы работать. Еще раз, прирост производительности незначителен, но затраты на обслуживание высоки.


«Некоторые интернированные строки имеют тенденцию становиться не интернированными». вау, это было бы ... странно Можете ли вы привести ссылку, пожалуйста?
Карл Смотриц

2
Хорошо, я думал, что вы имеете в виду Strings, которые фактически выходят из пулов стажера и попадают в кучу благодаря магии в JVM. Вы говорите, что == делает определенные классы ошибок программиста более вероятными.
Карл Смотриц

«Поэтому я предлагаю не полагаться на особый случай == для интернированных строк, но всегда использовать равно, как предполагал Гослинг». У вас есть прямая цитата или комментарий от Гослинга, утверждающий это? Если это так, то почему он вообще потрудился поместить intern () и использовать == в языке?

1
intern не подходит для прямого сравнения (==), даже если он работает, если обе строки интернированы. Замечательно уменьшить общий объем используемой памяти: когда одна и та же строка используется более чем в 1 месте.
tgkprog

12

Строковые литералы и константы интернированы по умолчанию. То есть "foo" == "foo"(объявлено строковыми литералами), но new String("foo") != new String("foo").


4
Итак, вопрос в том, когда мы должны использовать intern,
Ракеш Джуал

это было указано на stackoverflow.com/questions/1833581/when-to-use-intern и ряд других вопросов, некоторые из них со вчерашнего дня.
Божо

Дайте мне знать, если мое понимание этого утверждения: String literals and constants are interned by defaultправильно. new String("foo")-> Здесь один строковый литерал "foo" создается в пуле строк, а другой - в куче, поэтому создается всего 2 объекта.
ДКБ

8

Выучите Java String Intern - раз и навсегда

Строки в Java являются неизменными объектами по своему дизайну. Следовательно, два строковых объекта даже с одинаковым значением будут разными объектами по умолчанию. Однако, если мы хотим сэкономить память, мы можем указать на использование той же памяти с помощью концепции, называемой string intern.

Приведенные ниже правила помогут вам понять концепцию в четких терминах:

  1. Класс String поддерживает внутренний пул, который изначально пуст. Этот пул должен содержать строковые объекты только с уникальными значениями.
  2. Все строковые литералы, имеющие одинаковое значение, должны рассматриваться как один и тот же объект памяти, поскольку в противном случае они не имеют понятия различия. Следовательно, все такие литералы с одинаковым значением сделают одну запись в пуле и будут ссылаться на одну и ту же ячейку памяти.
  3. Объединение двух или более литералов также является литералом. (Поэтому правило № 2 будет применимо к ним)
  4. Каждая строка, созданная как объект (т. Е. Любым другим методом, кроме как в качестве литерала), будет иметь разные области памяти и не будет делать никаких записей в intern-пуле
  5. Конкатенация литералов с не-литералами сделает не-литерал. Таким образом, результирующий объект будет иметь новую ячейку памяти и НЕ будет делать запись в пуле.
  6. Вызов метода intern для строкового объекта либо создает новый объект, который входит в intern-pool, либо возвращает существующий объект из пула с таким же значением. Вызов любого объекта, которого нет в пуле, НЕ перемещает объект в пул. Это скорее создает другой объект, который входит в пул.

Пример:

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

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


Спасибо за # 3, я не знал :)
kaay

4

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

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

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

с одной стороны, в примере 1 мы находим, что все результаты возвращают true, потому что во время компиляции jvm поместит «test» в пул литеральных строк, если jvm find «test» существует, тогда он будет использовать существующий, в примере 1 все строки «test» указывают на один и тот же адрес памяти, поэтому в примере 1 будет возвращаться true. с другой стороны, в примере 2 метод substring () выполняется во время выполнения, в случае "test" == "! test" .substring (1), пул создаст два строковых объекта " test "и"! test ", поэтому они являются разными ссылочными объектами, поэтому в этом случае будет возвращено значение false, в случае" test "=="! test ".substring (1) .intern (), метод intern ( ) поместит "! test" .substring (1) "в пул литеральных строк,


3

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

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


2

Interned Strings избегают дублирования строк. Interning экономит оперативную память за счет увеличения процессорного времени для обнаружения и замены дублирующихся строк. Существует только одна копия каждой строки, которая была интернирована, независимо от того, сколько ссылок указывают на нее. Поскольку строки являются неизменяемыми, если два разных метода случайно используют одну и ту же строку, они могут совместно использовать копию одной строки. Процесс преобразования дублированных строк в разделяемые называется interning.String.intern (), дает вам адрес канонического мастера String. Вы можете сравнить интернированные строки с простым == (который сравнивает указатели) вместо равнокоторый сравнивает символы строки по одному. Поскольку строки являются неизменяемыми, внутренний процесс может дополнительно экономить пространство, например, не создавая отдельный литерал String для «pot», если он существует в качестве подстроки некоторого другого литерала, такого как «бегемот».

Чтобы увидеть больше http://mindprod.com/jgloss/interned.html


2
String s1 = "Anish";
        String s2 = "Anish";

        String s3 = new String("Anish");

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

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

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

ВЫВОД

s1 and s2 are same
s1 and s4 are same

2
String p1 = "example";
String p2 = "example";
String p3 = "example".intern();
String p4 = p2.intern();
String p5 = new String(p3);
String p6 = new String("example");
String p7 = p6.intern();

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

Когда две строки создаются независимо, это intern()позволяет сравнивать их, а также помогает создавать ссылку в пуле строк, если ссылка не существовала ранее.

Когда вы используете String s = new String(hi), java создает новый экземпляр строки, но когда вы используете String s = "hi", java проверяет, есть ли экземпляр кода «hi» в коде или нет, и если он существует, он просто возвращает ссылку.

Поскольку сравнение строк основано на ссылках, intern()помогает вам создать ссылку и позволяет сравнивать содержимое строк.

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

Но в случае p5, когда вы используете:

String p5 = new String(p3);

Только содержимое p3 копируется и p5 создается заново. Так что это не интернировано .

Таким образом, результат будет:

p1 and p2 are the same
p1 and p3 are the same
p1 and p4 are the same
p1 and p6 are the same when intern is used
p1 and p7 are the same

2
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    String s1 = "test";
    String s2 = new String("test");
    System.out.println(s1==s2);              //false
    System.out.println(s1==s2.intern());    //true --> because this time compiler is checking from string constant pool.
}

1

Метод string intern () используется для создания точной копии строкового объекта кучи в пуле строковых констант. Строковые объекты в пуле строковых констант автоматически интернируются, а строковые объекты в куче - нет. Основное использование создания интернов - это сохранение места в памяти и более быстрое сравнение строковых объектов.

Источник: Что такое строка интерна в Java?


1

Как вы сказали, этот intern()метод строки сначала найдет из пула String, если он найдет, то он вернет объект, который указывает на это, или добавит новую строку в пул.

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

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

s1И s2два объекта , указывающие на Струнный пул «Привет», и с помощью "Hello".intern()обнаружат , что s1и s2. Так что "s1 == s3"возвращает истину, как и в s3.intern().


Это на самом деле не дает много новой информации. Там уже исключенный ответ.
Александр,

0

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

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

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

Живописный вид введите описание изображения здесь

Шаг 1: Объект с данными 'Rakesh' создается в пуле кучи и строковой константы. Также s1 всегда указывает на объект кучи.

Шаг 2: Используя ссылку на объект кучи s1, мы пытаемся получить соответствующий объект пула строковой константы referenc s2, используя intern ()

Шаг 3: Преднамеренное создание объекта с данными 'Rakesh' в пуле строковых констант, на который ссылается имя s3

Поскольку оператор "==" предназначен для сравнения ссылок.

Получение ложной для s1 == s2

Становится правдой для s2 == s3

Надеюсь, это поможет!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.