Java

Интернирование строк в Java

Интернирование — это техника хранения только одной иммутабельной копии объекта для повторяющихся значений объекта. Каждая такая уникальная копия называется intern-ом.

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

Зачем это нужно

Интернирование строк позволяет оптимизировать работу с строками как в аспекте времени, так и памяти.

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

Если в коде объявлено две одинаковые по значению literal строки, то на самом деле ссылки будут указывать на один и тот же объект, так что оператор == вернет true при их сравнении.

JVM использует так называемый Java String Pool — специальный регион в куче, где хранятся все уникальные копии строк. JVM интернирует по-умолчанию все строки объявленные как literal (Compile-Time Constants). Например "Progbloom" будет интернирован, но не new String("Progbloom"), потому что вызов new всегда возвращает новый объект.

Garbage Collector также затрагивает и String Pool, поэтому те строки, которые больше не используются в приложении, будут очищены из памяти.

Сравнение строк

Когда мы уверены, что ссылки ведут на объект в String Pool (на intern), мы можем просто сравнить строки на идентичность за константное время, а не сравнивать символы строк, что заняло бы O(n) даже для итентичных по значению строк.

Однако, не следует сразу бросать equals() и бежать сравнивать идентичность строк по нескольким причинам:

  • Это случай premature optimization
  • Это подвержено ошибкам.

Мы можем заменить вызов equals() на проверку на идентичность (то есть с помощью оператора ==) только в следующих случаях:

  • Мы уверены, что полученные значения отличаются по значению. Например мы берем значения из Set, где по определению лежат только уникальные строки, но нам зачем-то понадобилось сравнить их
  • Мы работаем с Compile-Time константами
  • Мы явно получили intern строки

Метод intern()

Получить intern строки, а также интернировать строку мы можем с помощью вызова метода intern(). Данный метод проверяет, не содержится ли в String Pool уже такая строка, и возвращает её, а если не содержится, то создает её и кладет в String Pool и только затем возвращает.

Вызов intern() для literal строки не будет иметь эффекта и вернет тот же самый объект, однако с помощью этого метода мы можем вручную интернировать строки, созданные с помощью вызова new.

Практика

Проверим же высказанные выше утверждения на практике.

Сравним строки:

        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = new String("Hello");
        String s4 = s3.intern();
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
        System.out.println(s1 == s4); // true

Мы видим, что:

  • Сравнение строковых констант вернуло true, как и ожидалось, так как ссылки s1 и s2 указывают на один и тот же объект строки в String Pool
  • Сравнение строковой константы и строки созданной с помощью new вернуло false, так как ссылки s1 и s3 указывают на разные объекты
  • Сравнение строковой константы и intern() строки созданной с помощью new вернуло true, так как они являются одним и тем же объектом в String Pool

Заключение

Итак, мы изучили, что такое интернирование строк в Java. Следует заметить, что в Java интернируются не только строки, но и Boxed типы для следующих примитивов:

  • boolean (Смотрите константы Boolean.TRUE и Boolean.FALSEBoolean)
  • byte (См. класс ByteCache в классе Byte)
  • char для значений от 0 до 127 (см. класс CharacterCache в классе Character)
  • short и int для значений от -128 до 127 (см. классы ShortCache в классе Short и IntegerCache в Integer)

Так, например, выражение Integer.valueOf(1) == Integer.valueOf(1) возвращает true.

Использовать intern-ы строк напрямую (полученные с помощью вызова метода intern()) вам скорее всего не придется, так как, опять же, сравнение строк на идентичность — это плохо и лучше всегда использовать equals(), а другого случая использования интернов напрямую нет. Само же интернирование нужно в основном только JVM в целях оптимизации расхода памяти, кроме случаев, когда нам понадобилось явно интернировать строку.

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

Ваш адрес email не будет опубликован.