Подтвердить что ты не робот

Является ли String Literal Pool набор ссылок на объект String или набор объектов

Я все запутался после прочтения статьи на javaranch-сайте Кори МакГлоун, автором The SCJP Tip Line. названные строки, буквально и Руководство программиста SCJP Java 6 по Kathy Sierra (co-founder of javaranch) and Bert Bates.

Я попытаюсь процитировать то, что г-н Кори и г-жа Кэти Сьерри процитировали о String Literal Pool.

1. По словам г-на Кори МакГлоуна:

  • String Literal Pool - это набор ссылок, который указывает на объекты String.

  • String s = "Hello"; (Предположим, что в куче нет объекта с именем "Hello" ) создаст объект String "Hello" в куче и поместит ссылку на этот объект в пул строк (String Literal Pool) (константная таблица)

  • String a = new String("Bye"); (Предположим, что в куче нет объекта с именем "Bye", оператор new обязывает JVM создавать объект в куче.

Теперь объяснение оператора "new" для создания String и его ссылки немного запутанно в этой статье, поэтому я помещаю код и   объяснение самой статьи как таковой ниже.

public class ImmutableStrings
{
    public static void main(String[] args)
    {
        String one = "someString";
        String two = new String("someString");

        System.out.println(one.equals(two));
        System.out.println(one == two);
    }
}

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

Вот диаграмма, объясняющая это.

enter image description here

Значит ли это, что в String Literal Pool тоже есть ссылка на этот объект?

Вот ссылка на статью Кори МакГлон

http://www.javaranch.com/journal/200409/Journal200409.jsp#a1

2. По словам Кэти Сьерра и Берта Бейтса в книге SCJP:

  • Чтобы сделать Java более эффективной с точки зрения памяти, JVM выделил специальную область памяти под названием "String constant pool", когда компилятор встречает String Literal, он проверяет пул, чтобы увидеть, уже ли идентичная строка уже вышла или нет. Если нет, то создается новый строковый литерал.

  • String s = "abc";//Создает один объект String и одну ссылочную переменную....

    это прекрасно, но Теперь нижеследующее утверждение меня смутило.

  • String s = new String("abc")//Создает два объекта и одну ссылочную переменную.

    В книге говорится, что... новый объект String в нормальной (без пула) памяти и "s" будет ссылаться на него... тогда как дополнительный лимит "abc" будет помещен в пул.

    Вышеупомянутые строки в книге сталкиваются с тем, что было в статье Кори МакГлон.

    • Если String Literal Pool представляет собой набор ссылок на объект String, упомянутый Кори МакГлоном, буквальный объект "abc" будет помещен в пул, как указано в книге.

    • И где находится этот строковый литературный пул.

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

4b9b3361

Ответ 1

Я думаю, что основной смысл здесь понять различие между String объектом Java и его содержимым - char[] в частном value поле. String является в основном оболочкой вокруг массива char[], инкапсулируя его и делая невозможным изменение, поэтому String может оставаться неизменным. Также класс String запоминает, какие части этого массива фактически используются (см. Ниже). Все это означает, что вы можете иметь два разных объекта String (довольно легкий), указывающий на тот же char[].

Я покажу вам несколько примеров вместе с hashCode() каждого String и hashCode() внутреннего поля char[] value (я буду называть его текстом, чтобы отличить его от строки). Наконец, я покажу вывод javap -c -verbose вместе с постоянным пулом для моего тестового класса. Не путайте пул констант класса с пулом строковых литералов. Они не совсем то же самое. См. Также Понимание выхода javap для пула констант.

Предварительные условия

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

private int showInternalCharArrayHashCode(String s) {
    final Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);
    return value.get(s).hashCode();
}

Он напечатает hashCode() из char[] value, эффективно помогая нам понять, указывает ли этот конкретный String на тот же текст char[].

Два строковых литерала в классе

Давайте начнем с простейшего примера.

Код Java

String one = "abc";
String two = "abc";

Кстати, если вы просто пишете "ab" + "c", компилятор Java будет выполнять конкатенацию во время компиляции, и сгенерированный код будет точно таким же. Это работает только в том случае, если все строки известны во время компиляции.

Пул констант класса

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

Вот содержимое пула констант в нашем примере выше.

const #2 = String   #38;    //  abc
//...
const #38 = Asciz   abc;

Важно отметить различие между String постоянным объектом (#2) и кодированным в Юникоде текстом "abc" (#38), на который указывает строка.

Байт-код

Здесь генерируется код байта. Обратите внимание, что обе ссылки one и two назначаются с той же константой #2, указывающей на строку "abc":

ldc #2; //String abc
astore_1    //one
ldc #2; //String abc
astore_2    //two

Выход

Для каждого примера я печатаю следующие значения:

System.out.println(showInternalCharArrayHashCode(one));
System.out.println(showInternalCharArrayHashCode(two));
System.out.println(System.identityHashCode(one));
System.out.println(System.identityHashCode(two));

Не удивительно, что обе пары равны:

23583040
23583040
8918249
8918249

Это означает, что не только оба объекта указывают на тот же char[] (тот же текст внизу), поэтому тест equals() пройдет. Но даже больше, one и two - это те же самые ссылки! Таким образом, one == two также является истинным. Очевидно, что если one и two указывают на один и тот же объект, тогда one.value и two.value должны быть равны.

Литерал и new String()

Код Java

Теперь пример, который мы все ждали - один строковый литерал и один новый String, используя один и тот же литерал. Как это будет работать?

String one = "abc";
String two = new String("abc");

Тот факт, что константа "abc" используется два раза в исходном коде, должен дать вам некоторый намек...

Пул констант класса

То же, что и выше.

Байт-код

ldc #2; //String abc
astore_1    //one

new #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2    //two

Посмотрите внимательно! Первый объект создается так же, как и выше, не удивительно. Он просто принимает постоянную ссылку на уже созданный String (#2) из пула констант. Однако второй объект создается через обычный вызов конструктора. Но! Первый String передается как аргумент. Это можно декомпилировать для:

String two = new String(one);

Выход

Результат немного удивителен. Вторая пара, представляющая ссылки на объект String, понятна - мы создали два объекта String - один из них был создан для нас в постоянном пуле, а второй был создан вручную для two. Но почему на земле первая пара предполагает, что оба объекта String указывают на тот же массив char[] value?!

41771
41771
8388097
16585653

Становится понятно, когда вы смотрите на то, как String(String) работает конструктор (здесь значительно упрощается):

public String(String original) {
    this.offset = original.offset;
    this.count = original.count;
    this.value = original.value;
}

См? Когда вы создаете новый объект String на основе существующего, он повторно использует char[] value. String являются неизменяемыми, нет необходимости копировать структуру данных, которая, как известно, никогда не изменяется.

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

Изменение времени выполнения и intern()

Код Java

Предположим, вы сначала использовали две разные строки, но после некоторых изменений они все одинаковы:

String one = "abc";
String two = "?abc".substring(1);  //also two = "abc"

Компилятор Java (по крайней мере, мой) недостаточно умен, чтобы выполнить такую ​​операцию во время компиляции, посмотрите:

Пул констант класса

Внезапно мы закончили с двумя постоянными строками, указывающими на два разных константных текста:

const #2 = String   #44;    //  abc
const #3 = String   #45;    //  ?abc
const #44 = Asciz   abc;
const #45 = Asciz   ?abc;

Байт-код

ldc #2; //String abc
astore_1    //one

ldc #3; //String ?abc
iconst_1
invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;
astore_2    //two

Первая строка построена, как обычно. Вторая создается путем первой загрузки константы "?abc", а затем вызова substring(1) на ней.

Выход

Не удивительно, что у нас есть две разные строки, указывающие на два разных текста char[] в памяти:

27379847
7615385
8388097
16585653

Ну, тексты не совсем разные, equals() метод все равно даст true. У нас есть две ненужные копии одного и того же текста.

Теперь мы должны выполнить два упражнения. Сначала попробуйте запустить:

two = two.intern();

перед печатью хэш-кодов. Не только оба one и two указывают на один и тот же текст, но они являются одной и той же ссылкой!

11108810
11108810
15184449
15184449

Это означает, что пройдут тесты one.equals(two) и one == two. Также мы сохранили некоторую память, потому что текст "abc" появляется только один раз в памяти (вторая копия будет собрана мусором).

Второе упражнение несколько отличается, проверьте это:

String one = "abc";
String two = "abc".substring(1);

Очевидно, что one и two - это два разных объекта, указывающие на два разных текста. Но как результат выводит, что они оба указывают на тот же массив char[]?!?

23583040
23583040
11108810
8918249

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