Строка является наиболее важным типом в Java, помимо основных типов данных. Многие люди подумают, что он относительно прост. Но есть много вопросов на собеседовании, связанных со String. Позвольте мне наугад найти два вопроса на собеседовании и посмотреть, сможете ли вы правильно ответить на оба:
Q1:String s = new String("hollis");
Определено несколько объектов.
Q2: Как понятьString
изintern
метод
Вышеуказанные два являются общими вопросами, связанными с вопросами интервью и String, и многие люди обычно знают ответы.
A1: Если "hollis" уже существует в пуле констант, на него будет сделана ссылка напрямую, то есть в это время будет создан только один объект. Если "hollis" не существует в пуле констант, он будет создан первым и затем ссылаются, то есть есть два объекта.
A2: Когда экземпляр String str вызывает метод intern(), Java проверяет, есть ли строковая константа с тем же Unicode в пуле констант, если да, то возвращает ее ссылку, если нет, то добавляет Unicode, равный str, в константу строка пула и вернуть ее ссылку;
С двумя ответами вроде бы нет проблем, но если подумать, то кажется, что что-то не так. Судя по ответам на два вышеуказанных вопроса интервью, т.new String
Константный пул тоже будет проверяться, и если он есть, то на него будут ссылаться напрямую, если его нет, то он будет создан в константном пуле, так зачем стажер? Следующий код не имеет смысла?
String s = new String("Hollis").intern();
Если каждый раз, когда мы используем new для создания строки, она будет отправляться в пул строк для проверки, а затем возвращаться. Затем следующий код также должен вывести результатtrue
?
String s1 = "Hollis";
String s2 = new String("Hollis");
String s3 = new String("Hollis").intern();
System.out.println(s1 == s2);
System.out.println(s1 == s3);
Однако вывод приведенного выше кода (база jdk1.8.0_73):
false
true
Я не знаю. Прочитав этот код, умные читатели немного сбиты с толку. Что происходит?
Не волнуйся, просто послушай меня медленно.
Литералы и пулы констант времени выполнения
Чтобы повысить производительность и уменьшить нагрузку на память, JVM вносит некоторые оптимизации при создании экземпляров строковых констант. Чтобы уменьшить количество строк, создаваемых в JVM, класс String поддерживает постоянный пул строк.
В области методов области выполнения JVM область представляет собой постоянный пул времени выполнения, который в основном используется для хранениявремя компиляциигенерируются различныебуквальныйа такжеСимволическая ссылка.
Понимание структуры файла класса или создание кода Java.декомпилироватьДрузья могут знать, что в java-коде естьjavac
После компиляции файловая структура содержит частьConstant pool
из. Например следующий код:
public static void main(String[] args) {
String s = "Hollis";
}
После компиляции содержимое пула констант выглядит следующим образом:
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = String #21 // Hollis
#3 = Class #22 // StringDemo
#4 = Class #23 // java/lang/Object
...
#16 = Utf8 s
..
#21 = Utf8 Hollis
#22 = Utf8 StringDemo
#23 = Utf8 java/lang/Object
В пуле констант в приведенном выше файле класса есть несколько важных элементов:
#16 = Utf8 s
#21 = Utf8 Hollis
#22 = Utf8 StringDemo
Среди перечисленных выше константs
как упоминалось ранееСимволическая ссылка,а такжеHollis
как упоминалось ранеебуквальный. Содержимое части пула констант файла класса будет загружено в пул констант времени выполнения во время выполнения. О литералах см.Java SE Specifications
new String создает несколько объектов
Далее мы можем проанализироватьString s = new String("Hollis");
Объект создан.
В этом коде мы можем знать, что во время компиляцииСимволическая ссылкаs
а такжебуквальныйHollis
Будет добавлен в постоянный пул файла класса, а затем на этапе загрузки класса (см. Java для конкретного периода времениКогда «литерал» в новой строке («литерал») входит в пул строковых констант?), эти две константы войдут в пул констант.
Однако на этом «входном» этапе все константы, определенные во всех классах, не будут загружены напрямую, а будет выполнено сравнение.Если строки, которые необходимо добавить в пул строковых констант, уже существуют, то нет необходимости Затем загрузите строковый литерал.
Поэтому, когда мы говорим , мы говорим о процессе создания строкового литерала в струнный пул.
После разговора о времени компиляции пришло время запуска.new String("Hollis");
При выполнении строковый объект создается в куче Java, а строковый литерал, соответствующий этому объекту, сохраняется в пуле строковых констант. но,String s = new String("Hollis");
,символические ссылки на объектыs
Он хранится в стеке виртуальной машины Java и хранит ссылку на строковый объект, только что созданный в куче.
Итак, вы также знаете, почему следующий код выводит false.
String s1 = new String("Hollis");
String s2 = new String("Hollis");
System.out.println(s1 == s2);
потому что,==
в сравнении сs1
а такжеs2
Адреса объектов, созданных в куче, естественно, разные. Но если вы используетеequals
, то сравнение является буквальным содержанием, тогда вы получитеtrue
.
В разных версиях JDK отношение между кучей Java и пулом строковых констант тоже разное, здесь для удобства выражения оно нарисовано как две отдельные физические области. Подробности см. в спецификации виртуальной машины Java.
так,String s = new String("Hollis");
Ответ на создание нескольких объектов будет вам ясен.
"Объект" в пуле констант определяется во время компиляции и создается при загрузке класса. Если строковая константа уже существует в пуле констант при загрузке класса, этот шаг пропускается. Объекты в куче определяются во время выполнения и создаются при выполнении кода до нового.
Динамическое расширение пула констант времени выполнения
время компиляциигенерируются различныебуквальныйа такжеСимволическая ссылкаЭто важная часть источника пула констант времени выполнения, но не вся. Затем есть еще один случай, когда константы могут быть добавлены во время выполнения, например пул констант времени выполнения. То естьString
изintern
метод.
когдаString
вызов экземпляраintern()
метод, Java находит, есть ли в пуле констант строковая константа с таким же Unicode, если есть, возвращает ее ссылку, если нет, добавляет строку, Unicode которой равен str, в пул констант и возвращает ее ссылку;
intern() имеет две функции: первая — поместить строковый литерал в пул констант (если пул не существует), а вторая — вернуть ссылку на эту константу.
Давайте взглянем на загадочный пример в начале:
String s1 = "Hollis";
String s2 = new String("Hollis");
String s3 = new String("Hollis").intern();
System.out.println(s1 == s2);
System.out.println(s1 == s3);
Вы можете просто понять какString s1 = "Hollis";
а такжеString s3 = new String("Hollis").intern();
Делается то же самое (но на самом деле есть некоторые отличия, которые не будут здесь подробно описываться). Оба определяют строковый объект, затем сохраняют его строковый литерал в пуле констант и возвращают литеральную ссылку на определенную ссылку на объект.
заString s3 = new String("Hollis").intern();
, без звонкаintern
В данном случае s3 указывает на ссылку на объект, созданный JVM в куче (s2 на рисунке). Но при исполненииintern
метод, s3 будет указывать на строковую константу в пуле строковых констант.
Поскольку и s1, и s3 являются ссылками на литералы в пуле строковых констант, s1==s3. Однако ссылка на s2 является объектом в куче, поэтому s2!=s1.
Правильное использование стажера
Я не знаю, вы нашли это вString s3 = new String("Hollis").intern();
, по фактуintern
это лишнее?
потому что даже безintern
, Hollis также будет загружен в пул констант файла класса как литерал, а затем добавлен в пул констант среды выполнения. В каких сценариях его следует использовать?intern
Шерстяная ткань?
Прежде чем объяснять это, давайте взглянем на следующий код:
String s1 = "Hollis";
String s2 = "Chuang";
String s3 = s1 + s2;
String s4 = "Hollis" + "Chuang";
После декомпиляции полученный код выглядит следующим образом:
String s1 = "Hollis";
String s2 = "Chuang";
String s3 = (new StringBuilder()).append(s1).append(s2).toString();
String s4 = "HollisChuang";
Можно обнаружить, что склейка строк одинакова, s3 и s4 реализуются по-разному после компиляции компилятором. s3 преобразуется вStringBuilder
а такжеappend
, а s4 напрямую объединяется в новую строку.
Если вам интересно, вы также можете найти,String s3 = s1 + s2;
После компиляции в пуле констант есть две строковые константы:Hollis
,Chuang
(фактическиHollis
а такжеChuang
даString s1 = "Hollis";
а такжеString s2 = "Chuang";
определено), результат сплайсингаHollisChuang
не находится в постоянном пуле.
Если код толькоString s4 = "Hollis" + "Chuang";
, то постоянный пул будет иметь толькоHollisChuang
без "Холлис" и "Чуанг".
Причина в том, что постоянный пул для сохраненияОн был идентифицированбуквальное значение . То есть для конкатенации строк, чистой литеральной и литеральной конкатенации результат конкатенации будет сохранен в строке как константа.
Если в конкатенации строк один параметр будет не литералом, а переменной, вся операция конкатенации будет скомпилирована вStringBuilder.append
, в этом случае компилятор не может знать его детерминированное значение. Это можно определить только во время выполнения.
Итак, с этой функциейintern
Вот где это пригодится. То есть во многих случаях строки, которые мы получаем в программе, могут быть определены только во время выполнения и не могут быть определены во время компиляции, поэтому нет возможности добавить их в пул констант во время компиляции.
В настоящее время для типов строк, которые могут использоваться часто, используйтеintern
Определите, что каждый раз, когда JVM запускает этот код, она будет напрямую возвращать ссылку на литеральное значение в пуле констант, что может уменьшить создание большого количества строковых объектов.
как одинУглубленный анализ String#internПример в тексте:
static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];
public static void main(String[] args) throws Exception {
Integer[] DB_DATA = new Integer[10];
Random random = new Random(10 * 10000);
for (int i = 0; i < DB_DATA.length; i++) {
DB_DATA[i] = random.nextInt();
}
long t = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
}
System.out.println((System.currentTimeMillis() - t) + "ms");
System.gc();
}
В приведенном выше коде мы четко знаем, что будет много повторяющихся одинаковых строк, но значения этих строк можно определить только во время выполнения. Поэтому мы можем пройти толькоintern
Показано, что он добавляется в пул констант, что позволяет уменьшить повторное создание множества строк.
Суммировать
Вернемся к сомнению в начале статьи: судя по ответам на два вопроса интервью выше, то естьnew String
Константный пул также будет проверен, и если он есть, на него будет сделана прямая ссылка.Если он не существует, он будет создан в константном пуле, а затемintern
Что делаешь? Следующий код не имеет смысла?
String s = new String("Hollis").intern();
В intern «если есть, верните его ссылку напрямую», что означает, что ссылка буквального объекта напрямую возвращается к определенному объекту. Этот процесс не создает другой объект String в куче Java.
Действительно, то, как написан приведенный выше код, на самом деле бессмысленно использовать intern. Потому что буквальный Hollis загружается в пул констант времени выполнения как константа времени компиляции.
Причина вышеуказанных сомнений на самом деле вызвана не совсем понятным пониманием таких понятий, как пул строковых констант и литералы. На самом деле, некоторые вопросы таковы: вы знаете ответ на один вопрос, но если вы объедините несколько вопросов вместе, вы запутаетесь. В конечном счете, понимание знания все еще находится в точке, а не в связке.
Содержание данной статьи приветствуется к обсуждению, если есть какие-то необъективности прошу меня поправить.Примеры в статье специально даны для пояснения.Если есть какие-то неуместности прошу меня простить.