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

от admin

Строковый пул Java

Строковый пул, или множество строк, Java располагается в области памяти, именуемой Java Heapкуча, в которой хранятся все переменные, созданные программно вашим приложением. Естественно, появление этой структуры связано с оптимизацией способов хранения строк как объектов в языке.

Тип String – один из самых часто используемых в Java и довольно дорогой для хранения: 4-символьная строка занимает 56 байт памяти, т.е. реальные данные (4 символа) занимают только 14% выделенной под объект памяти. В связи с этим возникает необходимость оптимизировать способ хранения строк в памяти виртуальной машины. Так, было решено использовать подход Flyweight (приспособленец): если используется множество одинаковых данных (объектов), то гораздо экономнее создать только один экземпляр, чем несколько копий.

1. Работа пула на примерах.

Давайте рассмотрим следующий пример:

Выводом этого фрагмента кода будет следующее:

Как видите, str1 и str2 ссылаются на один и тот же объект (т.к. равенство == будет достигнуто только в случае равенства ссылок). Изобразить это можно следующим образом:

Как видите, если вы создаете новый объект String и вручную, т.е. литерами, задаете его значение ( «Hello, world» ), а объект с таким значением уже есть в пуле, то новая строка не создается, а просто переменная, являющаяся указателем, ссылается на уже созданный объект с тем же значением. Равенство, естественно, проверяется методом String.equals(String) . Таким образом, строковые переменные, имеющие при инициализации одинаковые значения, будут указывать на один и тот же объект.

Теперь вы понимаете, почему строки в Java являются неизменяемыми: если бы в одном потоке была создана строка, и с таким же значением в другом потоке была бы создана еще одна строка, то изменение строки в одном из потоков повлекло бы ее изменение в другом.

Сравним результат выполнения предыдущего фрагмента кода с этим:

Вывод в данном случае будет:

т.к. объекты, на которые указывают str1 и str3 уже отличаются, ведь для создания str3 был вручную вызван конструктор ( new ), что повлекло создание нового объекта. И теперь этот объект помещается не в строковом пуле, а в «обычном» сегменте памяти JVM.

Теперь рассмотрим такой пример:

Как было указано выше, строки в Java, как объекты, изменить нельзя. Поэтому когда в коде программы мы «изменили» str2 на самом деле был создан новый объект-строка с новым значением. При этом str1 , очевидно, остался указывать на тот же объект, что и раньше, поэтому равенство str1 == str2 на этот раз не выполнилось.

Теперь рассмотрим код, где для конкатенации используется StringBuffer . Его использование гарантирует то, что строка находится не в строковом пуле, а в «нормальной» области памяти.

Результатом выполнения этого кода будет следующий вывод:

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

2. Ручное помещение строки в пул

Как мы заметили, строковый пул не всегда используется автоматически. Но что же делать, если его использование может существенно уменьшить количество используемой памяти? Пусть мы создаем класс, описывающий адрес человека. Как известно, более 10 миллионов человек сегодня проживает в Москве. Представляете, как много места может занять использование отдельного объекта-строки для каждого из жителей Москвы? Для того, чтобы вручную поместить строку в пул, необходимо использовать метод String.intern() . Рассмотрим пример:

Результат выполнения этого кода будет представлять из себя следующий набор строк:

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

Но будьте аккуратны. До Java 7 в памяти существовала отдельная область PermGen, в которой и располагался пул, которому было отведено ограниченное количество памяти. Начиная с Java 7, строковый пул помещается в той же области кучи, что и все остальные объекты, что делает строки доступными для сборщика мусора. Указать объем отводимой под строки памяти (которые хранятся в виде хэш-мапа) можно с использованием опции XX:StringTableSize . Так Вы, фактически, создаете свой собственный пул строк, но впрочем, не забивайте голову.

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.

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

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

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

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

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

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

Читать:
Как опубликовать exchange протокол через микротик

Первая статья в серии 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), даже если есть строка с тем же значением.

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 1

Hey everyone! Программирование полно подводных камней. И нет практически ни одной темы, в которой вы не споткнетесь и не набьете шишки. Особенно это касается новичков. Уменьшить количество этого можно лишь одним способом — учиться. В частности это касается подробных разборов самых базовых тем. Сегодня продолжаю разбор вопросов 250+ с собеседований на Java-разработчика, которые хорошо охватывают базовые темы. Отмечу, что в списке есть и не совсем стандартные вопросы, позволяющие взглянуть на обычные темы под другим углом.

62. Что такое строковый пул и зачем он нужен?

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 2

В памяти в Java (Heap, о которой мы ещё поговорим) есть область — String pool , или строковый пул. Она предназначена для хранения строковых значений. Другими словами когда вы создаете некую строку, например через двойные кавычки: происходит проверка того, имеет ли строковой пул данное значение. Если имеет, переменной str присваивается ссылка на это значение в пуле. Если же не имеет, создастся новое значение в пуле, и ссылка на него будет присвоена переменной str . Рассмотрим пример: На экран будет выведено true . Мы помним, что == сравнивает именно ссылки — значит эти две ссылки ссылаются на одно и то же значение из строкового пула. Это сделано для того, чтобы не плодить множество одинаковых объектов типа String в памяти, ведь как мы помним, String — неизменяемый класс, и если у нас будет множество ссылок на одно и то же значение, плохого в этом ничего нет. Теперь невозможна ситуация, при которой изменение значения в одном месте приводит к изменениям сразу для нескольких других ссылок. Но тем не менее, если мы создадим строку через new : создастся отдельный объект в памяти, который будет хранить данное строковое значение (и не важно, есть ли у нас уже такое значение в строковом пуле). В качестве подтверждения: Мы получим два false , и это значит, что у нас тут три разные значения, на которые ссылаются ссылки. Собственно, поэтому рекомендуется создавать строки просто через двойные кавычки. Тем не менее, можно сложить (или получать ссылку) значения в строковой пул и при создании объекта через new . Для этого используем метод класса строки — intern() . Данный метод принудительно создает значение в строковом пуле, ну или получает ссылку на него, если оно уже хранится там. Вот пример: в результате мы получим в консоли три значения true , а значит, все три переменные ссылаются на одну и ту же строку.

63. Какие GOF-шаблоны применяются в строковом пуле?

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 3

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

64. Как разделить строку на части? Приведите пример соответствующего кода

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 4

Как мы видим, если бы не ограничение limit = 2 , последний элемент массива можно было бы разбить на три подстроки.

65. Почему массив символов лучше строки для сохранения пароля?

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 5

На самом деле не абракадабра, а: [C — имя класса — массив char , @ — разделитель, после которого — 7f31245a — шестнадцатеричный хешкод. 3. Официальный документ, руководство по архитектуре криптографии Java прямо указывает на хранение паролей в char[] вместо String : “Казалось бы, логично собрать и сохранить пароль в объекте типа java.lang.String . Однако здесь есть предостережение: объекты типа String неизменяемы, т. е. Не определены методы, позволяющие изменять (перезаписывать) или обнулять содержимое объекта String после использования. Эта функция делает String объекты непригодными для хранения конфиденциальной информации, такой как пароли пользователей. Вместо этого вы всегда должны собирать и хранить конфиденциальную информацию о безопасности в массиве символов.”

66. Дайте краткую характеристику Enum в Java
67. Может Enum реализовывать (implements) интерфейсы?
68. Может Enum расширять (extends) класс?
69. Можно ли создать Enum без экземпляров объектов?
70. Можно ли мы переопределить метод toString() для Enum?

Да, конечно вы можете переопределить метод toString() , чтобы определить конкретный способ отображения вашего enum при вызове метода toString (при переводе enum в обычную строку, например, для вывод в консоль или логи). На этом на сегодня у меня всё, до следующей части!Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 6Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 7 - 7

Похожие статьи