Базовый механизм объединения строк в Java?
Мне было любопытно, почему строки могут быть созданы без вызова new String()
, поскольку API упоминает, что это Object
из class
java.lang.String
Итак, как мы можем использовать String s="hi"
, а не String s=new String("hi")
?
Этот пост разъяснил использование оператора ==
и отсутствие new
и говорит, что это связано с String
литералами, которые интернированы или взяты из литерала пул JVM
, следовательно Strings
являются неизменяемыми.
При просмотре выражения типа
String s="hi"
в первый раз, что действительно происходит?
-
Является ли JVM
заменять его так: String s=new String("hi")
,
в котором создается объект и добавляется "hi"
в строку
литеральный пул, и поэтому последующие вызовы, такие как String s1="hi"
, являются
взято из пула?
-
Является ли это тем, как работает основной механизм? Если это так, то
String s=new String("Test");
String s1="Test";
то же, что и
String s="Test";
String s1="Test";
с точки зрения использования и эффективности памяти?
-
Кроме того, есть ли способ, с помощью которого мы можем получить доступ к String Pool, чтобы
проверьте, сколько литералов String
присутствует в нем, сколько места занято и т.д.?
Ответы
Ответ 1
-
String s="hi"
в первый раз, что действительно происходит?
Заменяет ли JVM это как String s=new String("hi")
, в котором создается объект и добавляется "привет" в строковый литерал пул, и поэтому последующие вызовы, такие как String s1 = "hi", берутся из пул?.
Нет. Что на самом деле происходит - литералы String разрешаются во время компиляции и интернированы (добавляются в пул строковых констант), как только класс загружается/инициализируется или лениво. Таким образом, они становятся доступными для классов в JVM.
Обратите внимание, что даже если у вас есть String со значением "hi"
в пуле констант Строки, new String("hi")
создаст еще одну String в куче и вернет ссылку.
- является
String s=new String("Test");
String s1="Test";
то же, что и
String s="Test";
String s1="Test";
с точки зрения использования памяти и эффективность?
Нет, в первом случае создаются строки 2 "Test". Один будет добавлен в пул констант String (если он там еще не присутствует), а другой в куче. Второй может быть GCed. Во втором случае только один строковый литерал присутствует в пуле констант String и есть 2 ссылки на него (s
и s1
).
- Также, если есть способ, с помощью которого мы можем получить доступ к пулу строк, как в проверьте, сколько строк в нем присутствует, занятое пространство и т.д. из программы или из любого инструмента мониторинга?
Я не думаю, что мы можем увидеть содержимое пула констант String. Мы можем просто предположить и подтвердить поведение, основанное на наших предположениях.
Ответ 2
Компилятор Java имеет специальную поддержку строковых литералов. Предположим, что это не так, тогда было бы очень громоздко создавать строки в исходном коде, вам нужно написать что-то вроде:
// Suppose that we would not have string literals like "hi"
String s = new String(new char[]{ 'h', 'i' });
Чтобы ответить на ваши вопросы:
-
Более или менее, и если вы действительно хотите узнать подробности, вам нужно изучить исходный код JVM, который вы можете найти в OpenJDK, но будьте осторожны, чтобы это было огромным и сложным.
-
Нет, эти два не эквивалентны. В первом случае вы явно создаете новый объект String
:
String s=new String("Test");
который будет содержать копию объекта String
, представленного литералом "Test"
. Обратите внимание, что никогда не рекомендуется писать new String("some literal")
в Java - строки неизменяемы, и никогда не нужно делать копию строкового литерала.
-
Я не знаю, что проверить в пуле строк.
Ответ 3
Это не тесно связано с объектом, но всякий раз, когда вы сомневаетесь в том, что сделает java-компилятор, вы можете использовать
javap -c CompiledClassName
чтобы напечатать то, что на самом деле происходит. (CompiledClassName из каталога, где CompiledClassName.class)
Чтобы добавить к ответу Jesper, на работе больше механизмов, например, когда вы объединяете строку из литералов или конечных переменных, она все равно будет использовать внутренний пул:
String s0 = "te" + "st";
String s1 = "test";
final String s2 = "te";
String s3 = s2 + "st";
System.out.println(s0==s1); //true
System.out.println(s3==s1); //true
Но когда вы объединяетесь с использованием не конечных переменных, он не будет использовать пул:
String s0 = "te";
String s1 = s0 + "st";
String s2 = "test";
System.out.println(s1 == s2); //false
Ответ 4
Ниже приводится небольшое упрощение, поэтому не пытайтесь приводить из него точные данные, но применяются общие принципы.
Каждый скомпилированный Java-класс содержит блок данных, который указывает, сколько строк было объявлено в этом файле класса, как долго каждый из них, и символы, принадлежащие всем им. Когда класс загружен, загрузчик классов создаст String[]
подходящего размера, чтобы удерживать все строки, определенные в этом классе; для каждой строки он генерирует char[]
подходящего размера, считывает соответствующее количество символов из файла класса в char[]
, создает String
, инкапсулируя эти символы, и сохраняет ссылку в классе String[]
.
При компиляции некоторого класса (например, Foo
) компилятор знает, какой строковый литерал он встречает первым, вторым, третьим, пятым и т.д. Если код говорит myString = "George";
, а Джордж - шестой строковый литерал, который появится в код в виде команды "строка строки литерала №6"; компилятор просто в момент, когда он генерирует код для этой команды, генерирует команду для извлечения ссылки шестой строки, связанной с этим классом.
Ответ 5
-
Как-то, но не совсем.
Строковые константы создаются и интернируются во время постоянного разрешения пула. Это происходит при первом выполнении байт-кода LDC
, который загружает строковый литерал. После первого выполнения JVM заменяет тег пула JVM_CONSTANT_UnresolvedString
тегом JVM_CONSTANT_String
таким образом, что в следующий раз LDC
вместо существующей строки будет создана существующая строка.
-
Нет. Первое использование "Test"
создаст новый строковый объект. Затем new String("Test")
создаст второй объект.
-
Да, используя агент обслуживания HotSpot. Вот example.
Ответ 6
Я считаю, что основным механизмом создания String является StringBuilder, который собирает объект String в конце. По крайней мере, я точно знаю, что если у вас есть строка, которую вы хотите изменить, например:
String str = "my String";
// and then do
System.out.println(str + "new content");
Итак, что это значит, он создает StrigBuilder из старого объекта и заменяет его новым, созданным из построителя. Вот почему более эффективно использовать память StringBuilder вместо обычной строки, к которой вы просто добавляете материал.
Существует способ доступа к уже созданному пулу String, который используется методом String.intern(). Он сообщает java использовать одно и то же пространство памяти для строк, которое дает ссылку на это место в памяти. Это также позволяет использовать оператор == для сравнения строк и более эффективно использовать память.
Ответ 7
Строковый пул, так как это пул строки, хранящийся в куче для exp:
String s="Test";
String s1="Test";
оба сохраняются в куче и ссылаются на один "Тест", таким образом, s1 = s,
в то время как
String s=new String("Test");
- это объект, который также хранится в куче, но в другой форме s1 = s
см. здесь