Давненько я ничего не писал. Было много конференций, тренинги и надо было еще и просто отдохнуть. Эту статью я хотел бы посвятить достаточно простому вопросу, который будоражит мир Java достаточно давно – использование оператора ‘+’ для строк. Как известно, раньше это считалось правилом дурного тона, потому что в ответ на применение ‘+’ для двух строк создавалась новая строка и содержимое первых двух копировалось в новую. Это добавляло как в использовании памяти, так и к работе CPU. Вдобавок страдал и сборщик мусора, работы которому мы подкидывали изрядно больше. Вопросы на эту тему были практически на каждом собеседовании и за незнание ответа “сурово карали”. 🙂
В качестве альтернативы Java разработчик должен создавать StringBuilder и добавлять в него строки, после чего вызывать toString() для формирования результата. Прошло время и компилятор научился в некоторых случаях заменять использование оператора ‘+’ на использование StringBuilder. Это отлично работает при самых распространенных случаях “плоских сложений” без какой-либо логики и ветвлений. И тут Java разработчики расслабили булки и подумали, что теперь все заживут счастливо. Но не тут то было…
StringBuilder прячет за собой работу с результирующей строкой (на деле с массивом char) и там не все так просто. Достаточно заглянуть внутрь реализации AbstractStringBuilder чтобы увидеть как осуществляется операция добавления строки. Если текущий размер массива символов маловат, то он расширяется на необходимое значение (но не меньше чем в 2 раза) с копированием уже имеющихся данных в новый массив. По умолчанию начальный размер массива составляет 16 либо равен длине первого переданного фрагмента. Получается, при неудачном стечении обстоятельств вы можете получить многократное расширение внутреннего массива с полным копированием. И это лишь немногим лучше старого сложения строк с созданием новой строки.
Выходит проблема никуда не исчезла, а просто притаилась. Но кого она может затронуть? Затрагивает она тех, у кого операции сложения со строками производятся в местах очень частого использования. Например, при генерации ключей или логировании в DEBUG режиме. И тот и другой случай легко обходятся: для ключей стоит использовать составной ключ в виде отдельного класса, а для логирования в DEBUG режиме проверять isDebugEnabled() (это возможно откроет глаза тем, кто не понимал зачем нужны эти методы, если уровень логирования и так конфигурируется). Более общее правило можно сформулировать так: “Каждый раз, используя сложение строк, задумайтесь над тем, как часто будет вызываться этот код. Если предполагаются частые вызовы, оптимизируйте решение или откажитесь от сложения.” Это простое правило сохранит вам много нервов и времени. 🙂
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Нет, смысл в том, чтобы каждый раз, складывая строки, задумываться о последствиях. И совершенно неважно как именно вы их складываете, с использованием билдера или без него.
Я правильно поняла, что весь смысл статьи сводится к тому, что стрингБилдер следует использовать и дальше, как собственно и использовали его нормальные люди вместо паровозика конкатенации?
Читайте внимательнее. Я не говорил про сложность алгоритмов, только про выделение памяти и копирование данных. Часто это приводит к тяжелой работе сборщика мусора и диким тормозам. Мы из-за такой проблемы выкинули в свое время commons-digester и писали парсинг XML руками.
Ну и что, что будет выделена дополнительная память. В сумме будет выделено не больше чем в 4 раза больше, чем нужно.
Проблема со строками в том, что сложение строк могло породить алгоритмы со сложность по скорости и расходованию памяти O(N*N). Со StringBuilder эти же алгоритмы будут работать со сложностью O(N). Конечно, StringBuilder избыточен. Но асимптотическая сложность алгоритмов, все равно O(N).
Placeholder-ы тоже замечательно сработают, если они применяются внутри после проверки текущего уровня логирования. Если же до, то все будет то же самое, только немного по-другому. 🙂
Я правильно понимаю, что при использовании slf4j, вместо isDebugEnabled() достаточно просто использовать placeholder-ы в строке формата? Или это не поможет?