Comments 27
Не делайте String#intern()
. Н И К О Г Д А.
Немного дополню. Хотите сэкономить память, когда у вас есть много одинаковых строк — сделайте мапу и для каждой новой строки проверяйте, нет ли её в мапе. Если есть, то просто используйте ту строку, которая в мапе. Если нет — положите новую строку в мапу. Ну или можно использовать 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 из сорсов, но это меркнет на фоне докладов и статей Шипилёва.
Надеюсь, что, теперь у вас есть более развёрнутый ответ на данную тематику и вы будете нести свет другим.
@Deprecated
так на него и не навесили.
Если она стала расширяемой и масштабируемой, то
@Deprecated
нет нужды вешать.Это торчащая наружу ручка от самой JVM и Вам туда не надо — если сильно надо — то можно подкрутить размер hash table. Точка.
Я интересоваkся пофикшены ли performance проблемы или нет. Так и не получил ответ(
А т.к. она не предназначена для работы вне 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. Так и не получил ответа(
Поэтому большинство страшилок, на которые дал ссылки 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)
А не наоборот.И никогда не используйте == для строк. За исключение каких-то совершенно невероятных и очень подробно прокомментированных случаев.
Как раз таки наоборот — такой лаконичный способ и безопасный, и не требует особой обработки в отличии от
someVar != null && someVar.equals("некая константа")
При использовании оператора new будет создана новая строка в пуле строк, даже если есть строка с тем же значением.
Нет, строка будет создана не в пуле, а в обычном хипе. В пул в рантайме строку можно засунуть как раз вызовом intern(). Строки из пула никогда не собираются GC, так что лучше не надо.
Спасибо за уточнение про хип. Но GC может удалять строки из пула.
https://stackoverflow.com/a/2433076
http://java-performance.info/string-intern-in-java-6-7-8/
https://github.com/FasterXML/jackson-core/issues/332
В этом случае результат 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()
не создает новую строку, а возвращает ту же строку.
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;
Java Challengers #2: Сравнение строк