Главная страница » Что такое пул строк java

Что такое пул строк java

  • автор:

String Pool в Java

Java даёт выбор между примитивными типами данных и объектными. Одни передаются по значению, другие по ссылке. Одни занимают предсказуемое количество памяти, другие не очень (конечно только если Вы не знаете размеры метаинформации класса, для которого хотите произвести расчёт). Под одних память выделяется на стеке, под другие в heap’е. Они сильно отличаются друг от друга

Элементы в пределах своего типа (примитивный или объектный) ведут себя похоже, независимо от конкретного типа данных. Значения int ведут себя так же, как и значения типа short. В объектных типах данных схожая ситуация. Но есть исключения. Например — объектный тип String.

Что такое String?

String — это класс в Java, то есть объектный тип. Он описывает строки и хранит их данные в массиве char.

Оговорка:
Тип char используется в старых версиях Java, например 8-ой. В Java 11 используется уже массив byte’ов.

Сколько памяти занимает String? Примитивный тип char в Java имеет размер 2 byte’а. То есть один символ занимает в памяти 2 байта. Теперь представьте — каждый раз когда мы используем строку в Java, будь-то имя пользователя или ссылку на какой-либо сайт, мы создаём в системе большой массив char. Это занимает память. В объектных типах помимо всех ссылок на объекты и примитивов, память занимает ещё и заголовочная информация класса.

Зачем это знать? Строки — самый популярный тип данных в Java. Огромное количество данных описывается строками. Ещё более интересн тот факт, что строки в одних и тех же программах часто повторяются.

String — это immutable тип. То есть, после создания объекта этого типа поменять его значение нельзя. Отсюда делаем вывод — смысла в создании новых объектов типа String со значениями, для которых объекты уже были созданы — нет. Это не целесообразно, потому что каждый раз выделять память под одно и тоже, что ещё и не возможно изменить — чревато чрезмерным потреблением памяти.

Создатели Java заранее позаботились об этой проблеме и сделали тип String не совсем обычным объектным типом.

Что такое строковый пул?

Строковый пул или String pool — это особое место в heap’е, куда попадают объекты типа String после их создания. Он выполняет функцию кеша строк. Каждый раз, когда Вы создаёте строку, она попадает в строковый пул. Если же на момент создания новой строки пул уже содержит такое же значение, то вместо создания нового объекта возвращается тот, что уже лежит в пуле.

У Вас есть возможность влиять на это поведение и не класть объекты в пул, если требуется.

Как работать со строковым пулом?

Разберёмся с тем как работает String pool на практике.

Обычно строки в Java программах объявляются так:

Что происходит в JVM за кадром? Остановитесь и подумайте. Если Вашим ответом будет что-то вроде — «В heap’е будет выделена память под строковый объект и ссылка на него будет возвращена и присвоена локальной переменной text» — Вы правы.

А что произойдёт тут? Если Вы думаете, что на обе локальные переменные будет выделена память в heap’е — Вы ошибаетесь. Именно в этом примере и видно результат работы пула строк.

В Java все строки, объявленные в виде литералов, то есть так:

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

Как убедиться в том, действительно ли обе переменные указывают на один и тот же объект? Очень просто. Достаточно посмотреть что будет выведено в результате следующей операции:

Не спешите набирать код, я Вам подскажу — ответом будет `true`. Это означает — обе переменные указывают на один и тот же объект.

А как тогда сделать так, чтобы строки в пул не попадали? Это тоже сделать просто. Используйте конструктор явно при создании строк. Вот так:

В этом случае, объект типа String будет создан, память под него будет выделена в heap’е, но в строковый пул он не попадёт. Это легко проверяется следующим примером:

Результатом работы кода выше будет `false`, потому что теперь ссылки указывают на два разных объекта.

Ну и наконец, как добавить строку в строковый пул после её создания? Для этого класс String содержит метод под названием `intern()`. Именно он отвечает за сохранение текущего объекта String в пул строк. Пример использования:

Зачем знать о строковом пуле?

Строковый пул несёт не только пользу. Если не знать о его существовании и принципах работы, можно легко получить дыру в безопасности приложения. Сделать это довольно просто — достаточно добавить в пул какой-нибудь пароль или логин. Содержимое строкового пула доступно в memory dump’ах, к которым Вы или кто-то другой может получить доступ.

Надеюсь эта статья поможет Вам писать код более осознанно, ведь, теперь Вы знаете, что за строками в Java стоит String pool.

Что такое классы-обертки?

Значение примитива становится объектом, чтобы выполнять различные операции ( .parseInt()).

Примитивы не имеют методов.

Объекты классов-оберток являются неизменяемыми (Immutable).

Что такое автоупаковка и автораспаковка?

Для присваивания ссылок-примитивов объектам их классов-оберток (и наоборот) не требуется ничего делать, все происходит автоматически

x = y; // автораспаковка

y = x * 123; // автоупаковка

Автоупаковка и распаковка не работают для массивов.

Автоупаковка — это механизм неявной инициализации объектов классов-оберток (Byte, Short, Integer, Long, Float, Double, Character, Boolean) значениями соответствующих им исходных примитивных типов (byte, short, int. ), без явного использования конструктора класса.

Автоупаковка происходит при прямом присваивании примитива классу-обертке (с помощью оператора =), либо при передаче примитива в параметры метода (типа класса-обертки).

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

Автоупаковка переменных примитивных типов требует точного соответствия типа исходного примитива типу класса-обертки.

Например, попытка упаковать переменную типа byte в Short, без предварительного явного приведения byte в short вызовет ошибку компиляции.

Автоупаковка констант примитивных типов допускает более широкие границы соответствия.

В этом случае компилятор способен предварительно осуществлять неявное расширение/сужение типа примитивов.

Неявное расширение/сужение исходного типа примитива до типа примитива соответствующего классу-обертке (для преобразования int в Byte, сначала компилятор самостоятельно неявно сужает int к byte) автоупаковку примитива в соответствующий класс-обертку.

Однако, в этом случае существуют два дополнительных ограничения:

a) присвоение примитива обертке может производится только оператором = (нельзя передать такой примитив в параметры метода без явного приведения типов)

b) тип левого операнда не должен быть старше чем Character, тип правого не должен старше, чем int.

Допустимо расширение/сужение byte в/из short, byte в/из char, short в/из char и только сужение byte из int, short из int, char из int. Все остальные варианты требуют явного приведения типов.

Дополнительной особенностью целочисленных классов-оберток созданных автоупаковкой констант в диапазоне -128 . +127 является то, что они кэшируются JVM. Поэтому такие обертки с одинаковыми значениями будут являться ссылками на один объект.

Приведение типов

Что такое явное и неявное приведение типов?
В каких случаях в JAVA нужно использовать явное приведение?

Каждое выражение и каждая переменная имеет строго определенный тип уже на момент компиляции. Механизм приведения типов (casting) — способ преобразования значения переменной одного типа в значение другого типа:

Неявные – выполняются автоматически (расширяющие преобразования, сужающие с потерей данных: int->float, long->float, long->double).

Явные – надо указывать тип (сужающие преобразования от типа с большей разрядностью к типу с меньшей разрядностью). Потеря данных (старшие биты будут потеряны).

Схема приведения типов

↓ИЗ | В→ boolean byte short char int long float double
boolean N N N N N N N
byte N Y C Y Y Y Y
short N C C Y Y Y Y
char N C C Y Y Y Y
int N C C C Y Y* Y
long N C C C C Y* Y*
float N C C C C C Y
double N C C C C C C

C — сужающее преобразование, требующее явного приведения

Y* — автоматическое расширяющее преобразование, в процессе которого значение может потерять некоторые из наименее значимых разрядов

Разновидности приведения:
    Тождественное (identity) — Преобразование выражения любого типа к точно такому же типу всегда допустимо и происходит автоматически.

Расширение (повышение, upcasting) примитивного типа (widening primitive) — Означает, что осуществляется переход от менее емкого типа к более ёмкому. Этот тип приведения всегда допустим и происходит автоматически.

Например, от типа byte (длина 8 бит) к типу int (длина 32 бита). Такие преобразование безопасны в том смысле, что новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом типе и таким образом не происходит потери данных.

Сужение (понижение, downcasting) примитивного типа (narrowing primitive) — Означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные .

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

Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны.

Сужение объектного типа (narrowing reference) — Означает нисходящее приведение, то есть приведение от предка к потомку (подтипу). Возможно только если исходная переменная является подтипом приводимого типа.

Требует явного указания типа. При несоответствии типов в момент выполнения выбрасывается исключение ClassCastException.

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

Для проверки возможности приведения нужно воспользоваться оператором instanceof :

Parent parent = new Child();

if (parent instanceof Child) <

Child child = (Child) parent;

Что такое пул интов?

Для более эффективного использования памяти, в JAVA используются так называемые пулы.

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

Особенность Integer-пула — он хранит только числа, которые помещаются в тип данных byte: от -128 до 127. Для остальных чисел пул не работает.

Integer i1 = 127;

Integer i2 = 127;

Integer i3 = 128;

Integer i4 = 128;

Какие нюансы у строк в Java?

Это неизменяемый (immutable) (не меняется после создания) и финализированный (без потомков) тип данных; потокобезопасный может использоваться в многопоточке

  • Все объекты класса String JVM хранит в пуле строк;
  • Объект класса String можно получить используя двойные кавычки;
  • Можно использовать оператор + для конкатенации строк;
  • Начиная с Java 7 строки можно использовать в конструкции switch.

Каждый объект можно привести к строке .toString

Что такое пул строк?

Пул строк – это набор строк хранящийся в Heap .

Пул строк возможен благодаря неизменяемости строк в Java и реализации идеи интернирования строк;

Интернирование строк — это механизм, при котором одинаковые литералы представляют собой один объект в памяти.

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

Когда для создания строки используются » , то сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка на неё;

При использовании оператора new создается новый объект String. Затем при помощи метода intern() эту строку можно поместить в пул или же получить из пула ссылку на другой объект String с таким же значением;

String r = new String(«I’m»);

String t = r.intern();

System.out.println(«q==w: » + (q==w)); //true

System.out.println(«q==r: » + (q==r)); //false

System.out.println(«q==t: » + (q==t)); //true

Пул строк является примером паттерна «Приспособленец» (Flyweight).

Почему String неизменяемый и финализированный класс?

  • Пул строк возможен только потому, что строка неизменяемая, таким образом виртуальная машина сохраняет больше свободного места в Heap, поскольку разные строковые переменные указывают на одну и ту же переменную в пуле. Если бы строка была изменяемой, то интернирование строк не было бы возможным, потому что изменение значения одной переменной отразилось бы также и на остальных переменных, ссылающихся на эту строку.
  • Если строка будет изменяемой, тогда это станет серьезной угрозой безопасности приложения. Например, имя пользователя базы данных и пароль передаются строкой для получения соединения с базой данных и в программировании сокетов реквизиты хоста и порта передаются строкой. Так как строка неизменяемая, её значение не может быть изменено, в противном случае злоумышленник может изменить значение ссылки и вызвать проблемы в безопасности приложения.
  • Неизменяемость позволяет избежать синхронизации: строки безопасны для многопоточности и один экземпляр строки может быть совместно использован различными потоками.
  • Строки используются classloader-ом и неизменность обеспечивает правильность загрузки класса.
  • Поскольку строка неизменяемая, её hashCode() кэшируется в момент создания и нет необходимости рассчитывать его снова. Это делает строку отличным кандидатом для ключа в HashMap т.к. его обработка происходит быстрее.

Какая основная разница между String, StringBuffer, StringBuilder?

Класс String String является неизменяемым ( immutable ) — модифицировать объект такого класса нельзя, можно лишь заменить его созданием нового экземпляра.

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

Класс StringBuilder был добавлен в Java 5 и он во всем идентичен классу StringBuffer за исключением того, что он не синхронизирован и поэтому его методы выполняются значительно быстрей .

Класс StringJoiner используется, чтобы создать последовательность строк, разделенных разделителем с возможностью присоединить к полученной строке префикс и суффикс:

StringJoiner joiner = new StringJoiner(«.», «prefix-«, «-suffix»);

for (String s : «Hello the brave world».split(» «)) <

Что такое сигнатура метода?

Объявление метода — это весь код, который описывает метод.

модификатор_доступа + тип_возвращаемого_значения + имя_метода(список_параметров) + исключения

Сигнатура — название метода и типы параметров в определенном порядке. methodName(double, int)

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

Насколько я понял модификатор доступа ни куда не входит.

Каким образом переменные передаются в методы, по значению или по ссылке?

Java передает параметры по значению. Всегда.

Скопировать значение внутри х и записать эту копию в у.

Ссылка А копируется в ссылку B. К объекту это не относится — у нас по-прежнему всего один объект. Но теперь есть две различных ссылки, контролирующие один и тот же объект Cat.

What is the Java string pool and how is "s" different from new String("s")? [duplicate]

What is meant by String Pool? And what is the difference between the following declarations:

Is there any difference between the storing of these two strings by the JVM?

Ciro Santilli OurBigBook.com's user avatar

Saurabh Gokhale's user avatar

5 Answers 5

The string pool is the JVM’s particular implementation of the concept of string interning:

In computer science, string interning is a method of storing only one copy of each distinct string value, which must be immutable. Interning strings makes some string processing tasks more time- or space-efficient at the cost of requiring more time when the string is created or interned. The distinct values are stored in a string intern pool.

Basically, a string intern pool allows a runtime to save memory by preserving immutable strings in a pool so that areas of the application can reuse instances of common strings instead of creating multiple instances of it.

As an interesting side note, string interning is an example of the flyweight design pattern:

Flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.

Andrew Hare's user avatar

The string pool allows string constants to be reused, which is possible because strings in Java are immutable. If you repeat the same string constant all over the place in your Java code, you can actually have only one copy of that string in your system, which is one of the advantages of this mechanism.

When you use String s = «string constant»; you get the copy that is in the string pool. However, when you do String s = new String(«string constant»); you force a copy to be allocated.

As mentioned by Andrew, the concept is called «interning» by the JLS.

Relevant passage from JLS 7 3.10.5:

Moreover, a string literal always refers to the same instance of class String. This is because string literals — or, more generally, strings that are the values of constant expressions (§15.28) — are «interned» so as to share unique instances, using the method String.intern.

Example 3.10.5-1. String Literals

The program consisting of the compilation unit (§7.3):

and the compilation unit:

produces the output:

  • If the method String.intern has previously been called on an instance of class String containing a sequence of Unicode code points identical to that given by the CONSTANT_String_info structure, then the result of string literal derivation is a reference to that same instance of class String.

  • Otherwise, a new instance of class String is created containing the sequence of Unicode code points given by the CONSTANT_String_info structure; a reference to that class instance is the result of string literal derivation. Finally, the intern method of the new String instance is invoked.

Bytecode

It is also instructive to look at the bytecode implementation on OpenJDK 7.

If we decompile:

we have on the constant pool:

  • 0 and 3 : the same ldc #2 constant is loaded (the literals)
  • 12 : a new string instance is created (with #2 as argument)
  • 35 : a and c are compared as regular objects with if_acmpne

The representation of constant strings is quite magic on the bytecode:

  • it has a dedicated CONSTANT_String_info structure, unlike regular objects (e.g. new String )
  • the struct points to a CONSTANT_Utf8_info Structure that contains the data. That is the only necessary data to represent the string.

and the JVMS quote above seems to say that whenever the Utf8 pointed to is the same, then identical instances are loaded by ldc .

I have done similar tests for fields, and:

  • static final String s = «abc» points to the constant table through the ConstantValue Attribute
  • non-final fields don’t have that attribute, but can still be initialized with ldc

Conclusion: there is direct bytecode support for the string pool, and the memory representation is efficient.

Java Challengers #2: Сравнение строк

У нас как всегда много опаздывающих к началу курса, так что только вчера провели второе занятие среди нового потока «Разработчик Java». Но это так, мелочи жизни, а пока что мы продолжаем публикацию серии статей Java Challengers, перевод которых подготовили для вас.

В Java класс String инкапсулирует массив char (прим. переводчика — с java 9 это уже массив byte , см. Компактные строки в Java 9). Говоря по простому, String — это массив символов, используемый для составления слов, предложений или других конструкций.

Инкапсуляция — это одна из самых мощных концепций объектно — ориентированного программирования. Благодаря инкапсуляции вам не нужно знать как работает класс String . Вам достаточно знать методы его интерфейса.

Когда вы смотрите на класс String в Java, вы можете увидеть как инкапсулирован массив char :

Чтобы лучше понять инкапсуляцию, представьте физический объект: машину. Нужно ли вам знать, как работает автомобиль под капотом, чтобы управлять им? Конечно, нет, но вы должны знать, что делают интерфейсы автомобиля: педаль газа, тормоза и рулевое колесо. Каждый из этих интерфейсов поддерживает определенные действия: ускорение, торможение, поворот налево, поворот направо. То же самое и в объектно — ориентированном программировании.

Первая статья в серии Java Challengers была про перегрузку методов, которая широко используется в классе String . Перегрузка может сделать ваши классы действительно гибкими:

Вместо того, чтобы пытаться понять, как работает класс String , эта статья поможет вам понять что он делает и как использовать его в вашем коде.

Что такое пул строк (String pool)

Класс String , возможно, наиболее часто используемый класс в Java. Если новый объект создавать в динамической памяти (memory heap) каждый раз, когда мы используем String , то мы потратим впустую много памяти. Пул строк (String pool) решает эту проблему, сохраняя только один объект для каждого значения строки.

strings-in-the-string-pool

Строки в пуле строк

Хотя мы создали несколько переменных String со значениями Duke и Juggy , но в динамической памяти (куче) создаётся и храниться только два объекта. Для доказательства посмотрите следующий пример кода. (Напомним, что в Java оператор » == » используется для сравнения двух объектов и определения того один и тот же это объект или нет.)

Этот код вернет true , потому что две переменные String указывают на один и тот же объект в пуле строк. Их значения одинаковые.

Исключение — оператор new

Теперь посмотрите на этот код — он выглядит похожим на предыдущий пример, но здесь есть отличие.

На основе предыдущего примера можно подумать, что этот код вернёт true , но это не так. Добавление оператора new приводит к созданию нового объекта String в памяти. Таким образом, JVM создаст два разных объекта.

Native-методы в Java — это методы, которые будут компилироваться с использованием языка C, обычно с целью управления памятью и оптимизации производительности.

Пулы строк и метод intern()

Для хранения строк в пуле используется способ, называемый «интернирование строк» (String interning).

Вот, что Javadoc говорит нам о методе intern() :

Метод intern() используется для хранения строк в пуле строк. Во-первых, он проверяет, существует ли уже созданная строка в пуле. Если нет, то создает новую строку в пуле. Логика пула строк основана на паттерне Flyweight.

Теперь, обратите внимание, что происходит, когда мы используем new для создания двух строк:

В отличие от предыдущего примера с ключевым словом new , в данном случае сравнение вернёт true . Это потому, что использование метода intern() гарантирует, что строка будет в пуле.

Метод equals в классе String

Метод equals() используется для того, чтобы проверить одинаковое или нет состояние двух классов. Поскольку equals() находится к классе Object , то каждый Java — класс наследует его. Но метод equals() должен быть переопределен, чтобы он работал правильно. Конечно, String переопределяет equals() .

Как вы видите, значение класса String сравнивается через equals() , а не через ссылку на объект. Не имеет значения, если ссылки на объекты разные; будут сравниваться состояния.

Наиболее распространенные методы String

Есть ещё одна вещь, которую вам нужно знать, прежде чем решить задачку на сравнение строк.

Рассмотрим наиболее распространённые методы класса String :

Решите задачку на сравнение строк

Давайте проверим, что вы узнали о классе String , решив небольшую задачку.

В этой задаче вы сравните несколько строк, используя изученные концепции. Глядя на код ниже, можете ли вы определить значение каждой переменной result ?

Какой будет вывод?

  • A: 02468
  • B: 12469
  • C: 12579
  • D: 12568

Правильный ответ приведён в конце статьи.

Что сейчас произошло? Понимание поведения String

В первой строке мы видим:

В этом случае результат false , потому что, когда метод trim() удаляет пробелы он создаёт новый String с помощью оператора new .

Здесь нет никакой тайны, строки одинаковы в пуле строк. Это сравнение возвращает true .

Использование new приводит к созданию двух новых строк и не важно равны их значения или нет. В этом случае сравнение будет false даже если значения одинаковые.

Поскольку мы использовали метод equals() , будет сравниваться значение строки, а не экземпляр объекта.

В этом случае, не имеет значение разные объекты или нет, поскольку сравнивается значение. Результат true .

Окончательно, мы имеем:

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

Распространенные ошибки со строками

Бывает трудно определить, указывают ли две строки на один и тот же объект или нет, особенно когда строки содержат одно и то же значение. Полезно помнить, что использование new всегда приводит к созданию нового объекта в памяти, даже если значения строк одинаковые.

Использование методов класса String для сравнения ссылок на объекты также может быть сложным. Особенность в том, что если метод изменяет что-то в строке, то будут разные ссылки на объекты.

Несколько примеров, которые помогут прояснить:

Это сравнение будет истинным, потому что метод trim() не создает новую строку.

В этом случае первый метод trim() генерирует новую строку, так как метод будет выполнять свою работу и поэтому ссылки будут разные.

Наконец, когда trim() выполнит свою работу, он создает новую строку:

Что нужно помнить о строках

Строки не изменяемые, поэтому состояние строки изменить нельзя.

Для экономии памяти JVM хранит строки в пуле строк. При создании новой строки JVM проверяет ее значение и указывает на существующий объект. Если в пуле нет строки с этим значением, то JVM создаёт новую строку.

Оператор » == » сравнивает ссылки на объект. Метод equals() сравнивает значения строк. То же правило будет применяться ко всем объектам.

При использовании оператора new будет создана новая строка в хипе (Прим. переводчика — в оригинале написано, что в пуле, но это не так, спасибо zagayevskiy), даже если есть строка с тем же значением.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *