Как стать автором
Обновить

Комментарии 27

Немного дополню. Хотите сэкономить память, когда у вас есть много одинаковых строк — сделайте мапу и для каждой новой строки проверяйте, нет ли её в мапе. Если есть, то просто используйте ту строку, которая в мапе. Если нет — положите новую строку в мапу. Ну или можно использовать Set, но там под капотом всё равно HashMap.


Когд все строки будут обработаны — можно смело отдать мапу сборщику мусора, а строки дальше будут жить в приложении и не занимать дополнительное место в памяти.

Вероятно, этому есть причина и Вы её знаете?
Честно, я сам с большой настороженностью отношусь к подобным «весьма затейливым штучкам из-под капота, за которые можно дёрнуть на ходу», но не менее настороженно я отношусь к безапелляционным заявлениям без внятной аргументации. Вот Вы что-то знаете, а я чего-то не знаю. Вы мне помогли? Скорее нет, чем да. Ваше предложение в таком виде воспринимается как религиозный принцип (вероятно, правильный по последствиям, но тщательно обессмысленный). «Trust me, I'm таки an engиneer!» — и хватит с Вас, убогие…
Впрочем, надеюсь, что Вы просто спешили, но не могли не предупредить.

Данная тема, intern(), является настолько избитой, что кажется странно о ней уже говорить снова и снова.


Как минимум странно говорить о ней, когда это уже 2ая часть подобной статьи, и в 1ой части не могли не сослаться (пусть и в комментариях) на доклад Алексея Шипилёва


The Lord of the Strings https://www.youtube.com/watch?v=HWkVJkoo1_Q


и, конечно же, Катехизис: java.lang.String https://youtu.be/SZFe3m1DV1A?t=1913 — здесь я специально привёл момент, где подробно и в деталях рассказывают почему intern() это зло.


+ чуть ниже я привёл ссылку на проблему с которой можно столкнуться при использовании jackson (а если у вас есть REST API или вы как-то работаете со json — то скорее всего вы столкнётесь именно с ним): https://github.com/FasterXML/jackson-core/issues/332
Почитайте и посмотрите какие проблемы (на пустом месте) и лишние доп. расходы всплывают при этом (и не у меня одного — этот prod профиль фееричен), но автору jackson эти доводы не зашли (сразу).


+ нельзя не упоминуть JVM Anatomy Park (всё того же А. Шипилёва) и в частности, что касается intern(): JVM Anatomy Park #10: String.intern()


Можно ещё добавить про то, что intern hash table, которая реализована на уровне VM не расширяется. Ещё в бытность java 6 проводил эксперимент с intern и пересборкой java из сорсов, но это меркнет на фоне докладов и статей Шипилёва.


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

Это ещё актуально для java 11? Вроде были подвижки, чтобы улучшить String.intern.

@Deprecated так на него и не навесили.

Я имею в виду performance проблемы в ней пофиксили или нет?
Если она стала расширяемой и масштабируемой, то @Deprecated нет нужды вешать.
Оно не сломано, просто вы его не правильно готовите.

Это торчащая наружу ручка от самой JVM и Вам туда не надо — если сильно надо — то можно подкрутить размер hash table. Точка.

Я не понимаю вас. Я не утверждал что оно сломано, или нет.
Я интересоваkся пофикшены ли performance проблемы или нет. Так и не получил ответ(
Окей — какие перформанс проблемы есть у intern с точки зрения его использования самой jvm? никаких — раньше был один размер таблицы — с увеличением внутренних нужд эту внутреннюю кухню чуть расширили.
А т.к. она не предназначена для работы вне jvm — то и проблемы индейцев инженеров jvm тоже не волнуют.
Она была не расширяема и использовала блокировки. Поэтому активное её использование, даже внутри JVM всегда было ограничено.
Поэтому мне было интересно узнать, сделали ли её более быстрой?

Далее. Метод String.intern() описан в javadoc последней версии Java — docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#intern()
В этом javadoc нет ни слова, о том, что её можно применять только в JVM. Метод не задепрекейчен, не помечен, какой-либо хитрой аннотацией.
Можно много обсуждать то, какие были идеи у отцов-основателей по поводу использования этого метода, но в ближайшее время этот метод никуда не исчезнет, как и приложения его использующие.
Это часть java specification и точка.
Всё что я хотел услышать от вас — остались ли с ним такие же performance проблемы в JDK 11, по сравнению с более ранними JDK. Так и не получил ответа(
Как я и подозревал, товарищи из Oracle не сидели сложа руки, а заиспользовали быструю расширяемую concurrent hash table для реализации пула строк. См JDK-8195097
Поэтому большинство страшилок, на которые дал ссылки vladimir_dolzhenko неактуальны для JDK11+.

Спасибо за ссылку. Берём jmh benchmark из JVM Anatomy Park #10: String.intern() и java 11 (build 11+28) запускаем — смотрим


Benchmark               (size)    Score     Error   Units
StrInt.chm               10000  2000,237 ± 73,646   ops/s
StrInt.chm:·gc.time      10000   100,000               ms
StrInt.intern            10000   448,418 ± 12,227   ops/s
StrInt.intern:·gc.time   10000    30,000               ms
StrInt.chm             1000000    10,792 ±  1,219   ops/s
StrInt.chm:·gc.time    1000000    33,000               ms
StrInt.intern          1000000     2,433 ±  0,218   ops/s
StrInt.intern:·gc.time 1000000    47,000               ms

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

Вот теперь большое спасибо.
Что же касается этого:


является настолько избитой, что кажется странно о ней уже говорить снова и снова.

Пока жизнь продолжается, пока в программирование (или в программирование на конкретном языке) приходят новые люди — странно будет как раз, если не будут снова и снова говорить об одном и том же. "Не долбить дятел не может. Если дятел не долбит, он спит, либо умер". То же относится и к любому языку программирования — если не говорят вновь и вновь о том, что "все уже знают", то этот язык или спит, или…

Вероятно, этому есть причина и Вы её знаете?

Да, причина есть. Если коротко, то строки, прошедшие через intern, хранятся в hash map который переполнен, а его размер увеличить нельзя. Поэтому всё это добро тормозит. Воркараунд можете посмотреть в моём комментарии выше.


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

Всегда делайте
"некая константа".equals(someVar)
А не наоборот.

И никогда не используйте == для строк. За исключение каких-то совершенно невероятных и очень подробно прокомментированных случаев.
С одной стороны да так программа не упадёт, с другой стороны случай с null возможно стоит обрабатывать особо.

Как раз таки наоборот — такой лаконичный способ и безопасный, и не требует особой обработки в отличии от


someVar != null && someVar.equals("некая константа")
В том случае если мы считаем нормальным то что someVar == null. Во многих других случаях такая ситуация может говорить о более глобальной ошибке в логике.
Надо проверить так проверьте. Отдельно от сравнения. Будет ясная четкая логика приложения.

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

Нет, строка будет создана не в пуле, а в обычном хипе. В пул в рантайме строку можно засунуть как раз вызовом intern(). Строки из пула никогда не собираются GC, так что лучше не надо.

сия статья была полезна для меня
Кому статья показалась интересной, будет интересно и выступление Алексея Шипилева на эту тему.

Видео

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

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


вот, тут я не понял, как это работает
result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1";
В этом случае результат false, потому что, когда метод trim() удаляет пробелы, он создаёт новый String с помощью оператора new.

Для строки с пробелами " powerfulCode " метод trim() создаст новый объект String.


System.out.println("duke".trim() == "duke".trim());
Это сравнение будет истинным, потому что метод trim() не создает новую строку.

Если пробелов нет "duke", то метод trim() не создает новую строку, а возвращает ту же строку.


javadoc trim():


returns — A string whose value is this string, with any leading and trailing white space removed, or this string if it has no leading or trailing white space.
Строка, значение которой равно этой строке с удаленными пробелами в начале и в конце или эту строку, если в ней нет пробельных символов

Из исходников trim():


return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
Зарегистрируйтесь на Хабре, чтобы оставить комментарий