Что такое пул строк
Перейти к содержимому

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

  • автор:

Пул строк

Пул строк (англ. string pooling ) относится к двум видам оптимизации компилятора, связанным со строками:

  1. Снижение объёма кода путём объединения одинаковых строк из разных модулей.
  2. Ленивые присваивания строк с использованием счётчика ссылок (copy-on-write).

Объединение строк из разных модулей

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

Чтобы не допустить роста объёма кода, многие компиляторы (в том числе компилятор C#) хранят литеральную строку в метаданных модуля только в одном экземпляре. Все упоминания этой строки в исходном коде компилятор заменяет ссылками на её экземпляр в метаданных. Благодаря этому заметно уменьшается размер модуля. Способ не нов — в компиляторах C/C++ этот механизм существует уже давно. В компиляторе Microsoft C/C++ это называется созданием пула строк (string pooling). Это ещё одно средство, позволяющее ускорить обработку строк.

Ленивые присваивания строк

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

Подобная оптимизация существует в языках со сборкой мусора в таком виде: строка является неизменным объектом, и присваивание a=b не создаёт новой строки. Недостаток этого решения в том, что программист должен знать и использовать внутренний механизм построения строк наподобие StringBuilder (Java).

Литература

  • Рихтер Дж. CLR via C#. Программирование на платформе Microsoft .NET Framework 2.0 на языке C#. Мастер-класс.
  • Оптимизации

Wikimedia Foundation . 2010 .

Главная

Пул строк — это конкретная реализация JVM концепции string interning.

Когда мы используем двойные кавычки для создания строки, сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка .
Пул строк возможен исключительно благодаря неизменяемости строк в Java и реализации идеи интернирования строк. Пул строк также является примером паттерна Приспособленец (Flyweight).
когда мы используем оператор new , мы принуждаем класс String создать новый объект строки, а затем мы можем использовать метод intern() для того, чтобы поместить строку в пул, или получить из пула ссылку на другой объект String с таким же значением.

Когда метод intern() вызван, если пул строк уже содержит строку, эквивалентную к нашему объекту, что подтверждается методом equals (Object), тогда возвращается ссылка на строку из пула. В противном случае объект строки добавляется в пул и ссылка на этот объект возвращается.

Сборка мусора :

До Java 7 , пул Java String помещался в пространство PermGen , которое имеет фиксированный размер — его нельзя развернуть во время выполнения и не подходит для сборки мусора .
Риск интернирования строк в PermGen (вместо кучи ) заключается в том, что мы можем получить ошибку OutOfMemory от JVM, если интернируем слишком много строк .

Начиная с Java 7, пул Java String хранится в пространстве Heap , собираемым JVM . Преимущество этого подхода заключается в уменьшении риска ошибки OutOfMemory, поскольку строки, на которые нет ссылок, будут удалены из пула, тем самым освобождая память.

работа пула строк :
 public class MyStringPool < public static void main(String[] args) < String s1 = "Cat"; // создаем строку и помещаем ее в pool строк String s2 = "Cat"; // создаем строку и помещаем ее в pool строк String s3 = new String("Cat"); // создаем новый обьект (не помещаем его в pool) System.out.println("s1 == s2 :"+(s1==s2)); System.out.println("s1 == s3 :"+(s1==s3)); > > s1 == s2 :true s1 == s3 :false 
*.intern() :
 String str1 = "first"; String str2 = "first"; String str3 = new String("first"); str3 = str3.intern(); System.out.println(str1 == str2); System.out.println(str2 == str3); System.out.println(str1 == str3); true true true 
Полезные ссылки:
  • «str».intern() in JavaDoc (Java 8)
  • «str».intern() in JavaDoc (Java 7)
  • «str».intern() in JavaDoc (Java 6)

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 программах объявляются так:

String text = “Hello World”;

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

String text = “Hello World”;
String text2 = “Hello World”;

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

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

“My String”

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

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

System.out.println(text == text2);

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

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

String text = new String(“text”);

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

String text = “Hello World”;
String text2 = new String(“Hello World”);
System.out.println(text == text2);

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

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

String text = “Hello World”;
String text2 = new String(“Hello World”);
System.out.println(text == text2); // выведет false
text2 = text2.intern(); // попытается положить текущую строку в пул строк, обнаружит что такое значение уже там есть и вернёт объект из строкового пула. То есть тот, на который указывает переменная text
System.out.println(text == text2); // выведет true

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

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

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

Разбираемся со строками в Java и Kotlin

Цель сегодняшнего поста раз и навсегда разобраться с таким типом данных как String. Вы все еще конкатенируете строки и не знаете зачем нужен StringBuilder? Тогда не проходите мимо – полученные знания помогут вам при собеседовании – такие вопросы часто любят задавать.

String Pool

Класс String представляет собой массив char. Каждый char занимает 2 байта. Кроме этого класс String имеет кэшируемый хэш, который занимает еще 4 байта. Кроме того, каждый объект имеет служебную информацию которая занимает 8 байт. И, если мы говорим о Java Development Kit 7 или ниже, то у класса String есть еще поля offset и length. Так класс String наиболее используемый типа данных, то многие инстансы таких объектов могут занимать значительную часть памяти. Поэтому, для сокращения потребляемой памяти JVM имеет, так называемый String pool, который представляет собой имплементацию паттерна проектирования Flyweight – шаблон проектирования, при котором объект, представляющий себя как уникальный экземпляр в разных местах программы, по факту не является таковым, так как размер потребляемой памяти может быть критичным для девайсов с маленьким размером свободной памяти, таких как мобильные устройства.

Создавая новую строку (используя двойные кавычки “”), JVM сначала ищет инстанс такой же строки (у которой такое же значение) в String pool. Если такое же значение найдено, то возвращается ссылка на это значение. В противном случае – создается новый инстанс в String pool и возвращается ссылка на него. Используя же конструктор, мы создаем новый инстанс в heap, а не в String pool:

Эта техника называется copy-on-write (COW). Идея в том, что когда мы вызываем необходимый объект, то возвращается ссылка на уже существующий вместо создания нового. Пример:

fun main(vars: Array)  val cat1 = "Cat" val cat2 = "Cat" val cat3 = String("Cat".toCharArray()) println(cat1 === cat2) println(cat1 === cat3) >

Результат такого кода:

true  false

При COW, когда мы пытаемся модифицировать объект через ссылку, то создается копия объекта, она изменяется и после этого возвращается ссылка на новый созданный объект.

fun main(vars: Array)

Cat CatDog false

Теперь, зная как работает JVM при изменении строки, рассмотрим такой код:

class User(val id: Int = 0, val firstName: String = "", val lastName: String = "") fun main(vars: Array) < val user = User() val building = "304a" val query = "SELECT id, firstName, lastName FROM Building " + building + " WHERE firstName color:#000000;font-size:18px" >При каждой конкатенации будет создаваться объект, поэтому лучше всего использовать StringBuilder или String Templates в Kotlin

val query = "SELECT id, firstName, lastName FROM Building $building WHERE firstName = $"

А теперь, фишка, о которой мало кто знает, но вы можете блеснуть на собеседовании. У класса String есть метод intern(). Метод intern() перед созданием объекта String смотрит есть ли этот объект в String Pool и возвращает его. Иначе создается новый объект в String Pool. Таким образом вы можете сохранить строку, созданную в куче в пуле строк. Пример:

fun main(args: Array)

Данный код вернет false

А код ниже уже вернет true

fun main(args: Array)

Вот так! Надеюсь эта статья была для вас полезной. Пишите комментарии в ВК и подписывайтесь на telegram-канал.

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

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