Что такое пул строк Java и как он отличается от нового String ( "s" )?

Что означает String Pool? И в чем разница между следующими объявлениями:

String s = "hello";
String s = new String("hello");

Есть ли разница между сохранением этих двух строк JVM?

Ответ 1

Пул строк - это конкретная реализация JVM концепции string interning:

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

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

Как интересная заметка, интернирование строк является примером мухи дизайна

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

Ответ 2

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

Когда вы используете String s = "string constant";, вы получаете копию, которая находится в пуле строк. Однако, когда вы выполняете String s = new String("string constant");, вы вынуждаете копировать выделение.

Ответ 3

JLS

Как упоминалось Andrew, эта концепция называется "интернированием" JLS.

Соответствующий отрывок из JLS 7 3.10.5:

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

Пример 3.10.5-1. Строковые литералы

Программа, состоящая из единицы компиляции (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

и блок компиляции:

package other;
public class Other { public static String hello = "Hello"; }

выводит результат:

true true true true false true

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

Ответ 4

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

Когда вы используете литерал, скажем String str = "abc";, используется объект в пуле. Если вы используете String str = new String("abc");, создается новый объект, но существующий строковый литерал может повторно использоваться либо на уровне JVM, либо на уровне байт-кода (во время компиляции).

Вы можете проверить это самостоятельно, создав много строк в цикле for и используя оператор == для проверки равенства объектов. В следующем примере string.value является закрытым для String и содержит строковый литерал. Поскольку он является закрытым, к нему нужно получить доступ через отражение.

public class InternTest {
    public static void main(String[] args) {
        String rehi = "rehi";
        String rehi2 = "rehi";
        String rehi2a = "not rehi";
        String rehi3 = new String("rehi");
        String rehi3a = new String("not rehi");
        String rehi4 = new String(rehi);
        String rehi5 = new String(rehi2);
        String rehi6 = new String(rehi2a);

        String[] arr  = new String[] { rehi, rehi2, rehi2a, rehi3, rehi3a, rehi4, rehi5, rehi6 };
        String[] arr2 = new String[] { "rehi", "rehi (2)", "not rehi", "new String(\"rehi\")", "new String(\"not rehi\")", "new String(rehi)", "new String(rehi (2))", "new String(not rehi)" };

        Field f;
        try {
            f = String.class.getDeclaredField("value");
            f.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }

        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                System.out.println("i: " +arr2[i]+", j: " +arr2[j]);
                System.out.println("i==j: " + (arr[i] == arr[j]));
                System.out.println("i equals j: " + (arr[i].equals(arr[j])));
                try {
                    System.out.println("i.value==j.value: " + (f.get(arr[i]) == f.get(arr[j])));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
                System.out.println("========");
            }
        }
    }
}

Вывод:

i: rehi, j: rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi, j: rehi (2)
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi, j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi, j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi, j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: rehi (2)
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: not rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: not rehi, j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String("not rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: not rehi, j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String(not rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("rehi"), j: new String("rehi")
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("rehi"), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: not rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("not rehi"), j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String("not rehi")
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String("not rehi"), j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String(not rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi), j: new String(rehi)
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String(rehi (2))
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: not rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(not rehi), j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String("not rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(not rehi), j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String(not rehi)
i==j: true
i equals j: true
i.value==j.value: true
========

Ответ 5

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

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

Оба объекта живут в куче. Ссылки на оба будут в стеке потоков.

http://www.journaldev.com/797/what-is-java-string-pool дает четкое представление о том, как это достигается

Ответ 6

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

   public class Program
   {
       public static void main(String[] args)
       {
          String str1 = "Hello";  
          String str2 = "Hello"; 
          System.out.print(str1 == str2);
       }
   }

Выход: true

К сожалению, когда вы используете

String a=new String("Hello");

Объект String создается из пула строковых литералов, даже если в пуле уже существует равная строка.

public class Program
{
    public static void main(String[] args)
    {
       String str1 = "Hello";  
       String str2 = new String("Hello");
       System.out.print(str1 == str2 );

    }
}

Выход: false