Pull to refresh

Все о String.intern()

Reading time 4 min
Views 118K
Original author: Ethan Nicholas
Думаю, что многие Java-разработчики знают или хотя бы слышали о методе String.intern(). Но далеко не каждый использует его в своих приложениях или представляет в каких случаях он полезен и полезен ли вообще. Так было и со мной до тех пор пока я не столкнулся с этим методом в одном из проектов. В тот момент я захотел узнать смысл и особенности его использования и набрел на одну весьма интересную статью ведущего разработчика Yahoo! по имени Ethan Nicholas, переводом которой теперь хочу поделиться с той частью Хабра-сообщества, которая не безразлична к языку Java.

Тем, кто знает об этом методе лишь понаслышке, добро пожаловать под кат.


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

Сегодня я просматривал исходный код Xerces (XML-парсер, включенных в Java) и наткнулся на строку, которая меня очень удивила:

com.sun.org.apache.xerces.internal.impl.XMLScanner:395
protected final static String fVersionSymbol = "version".intern();

Далее я нашел еще несколько строк, определенных как эта, и каждая из них была интернирована. Так что же такое intern()? Ну, как вы, несомненно, знаете, существует два различных способа для сравнения объектов в Java. Вы можете использовать оператор ==, или же вы можете использовать метод equals(). Оператор == сравнивает ссылаются ли две ссылки на один и тот же объект, в то время как equals() сравнивает содержат ли два объекта одни и те же данные.

Одним из первых уроков, который вы усваиваете при изучении Java является то, что обычно для сравнения двух строк вы должны использовать equals(), а не ==. Если сравнить, скажем, new String("Hello") == new String("Hello"), то в результате получится false, потому что это два разных экземпляра класса. Если же вы используете equals(), то получите true, как и ожидаете. К сожалению, equals() может оказаться довольно медленным, поскольку он выполняет посимвольное сравнение строк.

Т.к. оператор == проверяет идентичность (identity), все, что он должен сделать — это сравнить два указателя, и, очевидно, это будет гораздо быстрее, чем equals(). Так что если вы собираетесь сравнивать одни и те же строки многократно, вы можете получить значительное преимущество в производительности за счет использования проверки идентичности объектов вместо сравнения символов.

Основной алгоритм:

1) Создать множество (hash set) строк
2) Проверить, что строка (как последовательность символов), с которой вы имеете дело, уже в множестве
3) Если да, то использовать строку из множества
4) В противном случае, добавить эту строку в множество и затем использовать ее

При использовании этого алгоритма гарантируется, что если две строки являются идентичными последовательностями символов, они являются одним экземпляром класса. Это означает, что вы можете спокойно сравнивать строки, используя == вместо equals(), получая при этом значительные преимущества производительности при многократно повторяющихся сравнениях.

К счастью Java уже включает в себя реализацию этого алгоритма. Это метод intern() в классе java.lang.String. Выражение new String("Hello").intern() == new String("Hello").intern() возвращает true, в то время как без использования intern() возвращается false.

Так почему же я так удивился, увидев
protected final static String fVersionSymbol = "version".intern();
в исходном коде Xerces? Очевидно, что эта строка будет использоваться для многократных сравнений. Имеет ли смысл интернировать ее?

Конечно, имеет. Вот почему Java уже это делает. Все строки-константы, которые встречаются в классе автоматически интернированы. Сюда входят как собственные константы (например, приведенная выше строка "version"), так и другие строки, которые являются частью формата файла класса — имена классов, сигнатуры методов и так далее. Это распространяется даже на выражения: "Hel" + "lo" обрабатывается javac точно так же, как "Hello", поэтому "Hel" + "lo" == "Hello" возвращает true.

Таким образом, результатом вызова intern() для строки-константы типа "version", по определению, будет точно тот же объект, который вы объявили. Другими словами "version" == "version".intern() всегда истинно. Вам нужно интернировать строки тогда, когда они не являются константами, и вы хотите иметь возможность быстро сравнить их с другими интернированными строками.

Также при интернировании строк можно получить преимущество в использовании памяти, т.к. вы храните в ней лишь один экземпляр последовательности символов строки, независимо от того, сколько раз вы на эту последовательность ссылаетесь. Это основная причина того, почему строковые константы файла класса интернированы: подумайте о том, сколько классов ссылаются, например, на java.lang.Object. Имя класса java.lang.Object должно появиться в каждом из этих классов, но, благодаря магии intern(), оно появляется в памяти лишь в одном экземпляре.

Вывод? intern() является полезным методом и может сделать жизнь легче — но убедитесь, что вы используете его должным образом.

От переводчика
Прошу простить за то, что пару раз исказил исходный текст, чтобы сделать его более понятным (как мне казалось).

Большое спасибо хабраюзеру nolled, который пригласил меня в Хабрасообщество.

Update
Думаю, что следующая информация, которую я узнал из других источников будет здесь не лишней:

1. Пул строк хранится в области «Perm Gen», которая зарезервирована для non-user объектов JVM (классы и пр). Если этого не учитывать, вы можете неожиданно получить OutOfMemory Error.
2. Интернированные строки не хранятся вечно. Строки, на которых нет ссылок, также удаляются сборщиком мусора.
3. В большинстве случаев вы не получите существенного прироста производительности от использования intern() — если сравнение строк не является основной (или очень частой) операцией вашего приложения и сравниваемые строки разные по длине.
Tags:
Hubs:
+42
Comments 33
Comments Comments 33

Articles