StringBuilder против конкатенации строк в toString () в Java
Учитывая 2 реализации toString() ниже, какая из них предпочтительна:
Что еще более важно, учитывая, что у нас всего 3 свойства, это может не иметь значения, но в какой момент вы переключитесь с + concat на StringBuilder ?
100 символов, вы можете установить StringBuilder на этот размер, и ему никогда не придется расширяться внутри.
20 ответов
Версия 1 предпочтительнее, поскольку она короче и компилятор фактически превратит его в версию 2 — никакой разницы в производительности.
Что еще более важно, учитывая, что у нас всего 3 свойства, это может не иметь значения, но в какой момент вы переключаетесь с concat на построитель?
В момент конкатенации цикла — обычно это когда компилятор не может заменить StringBuilder сам по себе.
500 итераций за 20 секунд до 35 000 итераций за 5 секунд. Я был просто в шоке от разницы. Были также некоторые конкатенации целых чисел, которые я заменил вызовами String.format, что, вероятно, повысило производительность; честно говоря, я не уверен, какое изменение оказало наибольшее влияние. Но полагать, что «+» всегда просто отлично, — ошибка.
+ быстрее, чем Java 9, если только JVM не знает, как его оптимизировать (например, конкатенация в цикле).
Я проверил байт-код для следующего кода (в Java 17):
Версия + просто вызывает динамическую функцию makeConcatWithConstants и передает аргумент метода
В то время как версия StringBuilder должна делать это «честным» способом.
Я думаю, теперь мы можем понять, почему + быстрее.
В большинстве случаев вы не увидите реальной разницы между двумя подходами, но легко построить наихудший сценарий, подобный этому:
Проблема в том, что to + = append к строке восстанавливает новую строку, поэтому она стоит чего-то линейного по отношению к длине ваших строк (сумма обоих).
Итак — на ваш вопрос:
Второй подход будет более быстрым, но менее читаемым и сложным в обслуживании. Как я уже сказал, в вашем конкретном случае вы, вероятно, не заметите разницы.
. потому что он короткий и читаемый.
Я бы не оптимизировал это по скорости, если вы не используете его внутри цикла с очень большим числом повторов и измерили разницу в производительности.
Я согласен, что если вам нужно вывести много параметров, эта форма может запутаться (как говорится в одном из комментариев). В этом случае я бы переключился на более читаемую форму (возможно, используя ToStringBuilder apache-commons — взято из ответа matt b) и снова игнорируйте производительность.
Начиная с Java 1.5, простое объединение одной строки с помощью «+» и StringBuilder.append () генерирует точно такой же байт-код.
Поэтому для удобства чтения используйте «+».
- многопоточная среда: StringBuffer
- конкатенация в циклах: StringBuilder / StringBuffer
У меня также было столкновение с моим боссом по поводу того, использовать ли append или +. Поскольку они используют Append (я до сих пор не могу понять, как они говорят, каждый раз, когда создается новый объект). Поэтому я подумал о некоторых исследованиях и разработках, хотя мне нравятся объяснения Майкла Боргвардта, но я просто хотел показать объяснение, если кому-то действительно нужно будет это знать в будущем.
И разборка вышеуказанного класса выглядит как
Из двух приведенных выше кодов видно, что Майкл прав: в каждом случае создается только один объект SB.
При использовании последней версии Java (1.8) разборка ( javap -c ) показывает оптимизацию, введенную компилятором. + , а также sb.append() будет генерировать очень похожий код. Однако стоит проверить поведение, если мы используем + в цикле for.
Добавление строк с помощью + в цикле for
ByteCode: ( for отрывок цикла)
Добавление строк с помощью stringbuilder.append
ByteCdoe: ( for отрывок из цикла)
Однако есть небольшая очевидная разница . В первом случае, когда использовался + , новый StringBuilder создается для каждой итерации цикла for, а сгенерированный результат сохраняется путем выполнения вызова toString() (с 29 по 41). Таким образом, вы генерируете промежуточные строки, которые вам действительно не нужны при использовании оператора + в цикле for .
В Java 9 версия 1 должна быть быстрее, потому что она преобразована в вызов invokedynamic . Более подробную информацию можно найти в JEP-280:
Идея состоит в том, чтобы заменить весь танец добавления StringBuilder простым вызовом invokedynamic к java.lang.invoke.StringConcatFactory, который будет принимать значения, требующие объединения.
По соображениям производительности использование += (конкатенации String ) не рекомендуется. Причина в следующем: Java String неизменяемый, каждый раз, когда выполняется новая конкатенация, создается новый String (у нового уже есть отпечаток, отличный от старого в пуле строк). Создание новых строк оказывает давление на сборщик мусора и замедляет работу программы: создание объекта обходится дорого.
Код ниже должен сделать его более практичным и понятным одновременно.
Результаты пробега представлены ниже.
Без учета результатов для 1 конкатенации (JIT еще не выполнял свою работу), даже для 10 конкатенаций имеет значение снижение производительности; для тысяч конкатенаций разница огромна.
Уроки, извлеченные из этого очень быстрого эксперимента (легко воспроизводимого с помощью приведенного выше кода): никогда не используйте += для объединения строк вместе, даже в самых простых случаях, когда требуется несколько объединений (как уже говорилось, создание новых строк в любом случае обходится дорого. и давит на ГХ).
Это зависит от размера строки.
версия байт-кода Java: 8
java.version: 1.8.0_144
[str1.concat (str2)]
среднее время для 10000 конкатенаций: 0,096 мс в среднем
среднее время для 10000 конкатенаций: 0,185 мс в среднем
среднее время для 10000 конкатенаций: 0,327 мс в среднем
среднее время для 10000 конкатенаций: 0,501 мс в среднем
среднее время для 10000 конкатенаций: 0,656 мс в среднем
Созданная строка длиной: 1950000 в 17745 мс
[str1 + = str2]
среднее время для 10000 конкатенаций: 0,21 мс в среднем
среднее время для 10000 конкатенаций: 0,652 мс в среднем
среднее время для 10000 конкатенаций: 1,129 мс в среднем
среднее время для 10000 конкатенаций: 1,727 мс в среднем
среднее время для 10000 конкатенаций: 2,302 мс в среднем
Созданная строка длиной: 1950000 в 60279 мс
[str1.append (str2)]
среднее время для 10000 конкатенаций: 0,002 мс в среднем
среднее время для 10000 конкатенаций: 0,002 мс в среднем
среднее время для 10000 конкатенаций: 0,002 мс в среднем
среднее время для 10000 конкатенаций: 0,002 мс в среднем
среднее время для 10000 конкатенаций: 0,002 мс в среднем
Созданная строка длиной: 1950000 в 100 мс
По мере увеличения длины строки увеличивается время конкатенации += и .concat , причем последнее является более эффективным, но все же непостоянным
Вот где StringBuilder определенно нужен.
PS: Я не думаю, что когда использовать StringBuilder в Java действительно дублирует это.
В этом вопросе говорится о toString() , который в большинстве случаев не выполняет конкатенацию огромных строк.
JDK 9/JEP 280: конкатенация строк никогда больше не будет прежней
И снова здравствуйте. Как мы уже писали, на следующей неделе стартует новая группа обучения по курсу «Разработчик Java», по устоявшейся традиции делимся с вами переводом интересного материала по теме.
Начиная с JDK 9 конкатенация строк претерпела значительные изменения.
JEP 280 («Indify String Concatenation») был реализован в рамках JDK 9 и, в соответствии с разделом «Summary»: «Изменяет статическую последовательность байт-кода конкатенации строк, сгенерированную javac, для использования вызовов invokedynamic к функциям библиотеки JDK». Влияние, которое это оказывает на конкатенацию строк в Java, легче всего заметить, посмотрев на javap-вывод классов, использующих конкатенацию строк, которые скомпилированы в JDK до JDK 9 и после JDK 9.
/>
Для первой демонстрации будет использоваться простой Java-класс с именем «HelloWorldStringConcat».
Ниже показано сопоставление различий для -verbose вывода javap для метода main(String) класса HelloWorldStringConcat при компиляции с JDK 8 (AdoptOpenJDK) и JDK 11 (Oracle OpenJDK). Я выделил несколько ключевых различий.
JDK 8 javap-вывод
JDK 11 javap-вывод
Раздел «Description» в JEP 280 описывает это различие: «Идея состоит в том, чтобы заменить весь танец присоединения StringBuilder простым вызовом invokedynamic к java.lang.invoke.StringConcatFactory, который будет принимать значения, требующие объединения». В этом же разделе показано аналогичное сравнение скомпилированного вывода для аналогичного примера конкатенации строк.
Скомпилированный вывод с JDK 11 для простой конкатенации — это не просто меньшее количество строк, чем в выводе с JDK 8; у него также меньше «дорогих» операций. Потенциальное улучшение производительности может быть достигнуто за счет того, что нет необходимости в обертывании примитивных типов и не требуется создавать множество дополнительных объектов. Одним из основных мотивов этого изменения было «заложить основу для создания оптимизированных обработчиков конкатенации строк, реализуемых без необходимости изменения компилятора Java-to-bytecode» и «включить будущие оптимизации конкатенации строк без дополнительных изменений в байт-коде генерируемом javac. «
Есть интересное следствие этого с точки зрения использования StringBuffer (которому мне в любом случае сложно найти хорошее применение) и StringBuilder. В JEP 280 в “Non-Goal” было заявлено не «вводить какие-либо новые API-интерфейсы для String и/или StringBuilder, которые могли бы помочь в создании более эффективных стратегий перевода». В связи с этим для простой конкатенации строк, такой как в примере в начале этого поста, явное использование StringBuilder и StringBuffer фактически исключает для компилятора возможность использовать фичу, представленную в JEP 280, которую мы обсуждаем в этом посте.
Следующие два листинга показывают аналогичные реализации простого приложения, показанного выше, но вместо конкатенации строк они используют StringBuilder и StringBuffer соответственно. Когда javap -verbose выполняется для этих классов после того, как они скомпилированы с JDK 8 и с JDK 11, в main(String []) методах нет существенных различий.
Явное использование StringBuilder в JDK 8 и JDK 11 одинаково
Явное использование StringBuffer в JDK 8 и JDK 11 одинаково
JDK 8 и JDK 11 Обработка зацикленных конкатенаций строк
Для последнего примера изменений в JEP 280 в действии я использую пример кода, который может нарушить восприимчивость некоторых разработчиков Java и выполнить конкатенацию строк в цикле. Имейте в виду, что это только иллюстративный пример, и все будет хорошо, но не пытайтесь повторять это дома.
В презентации «Enough java.lang.String to Hang Ourselves . », доктор Хайнц М. Кабуц (Heinz M. Kabutz) и Дмитрий Вязеленко (Dmitry Vyazelenko) обсуждают внесенные изменения в конкатенацию строк Java и кратко их обобщают, “+ больше не компилируется в StringBuilder”. На слайде «Lessons from Today» они заявляют: «Используйте + вместо StringBuilder, где это возможно» и «Перекомпилируйте классы для Java 9+».
Изменения, реализованные в JDK 9 с JEP 280, «позволят в будущем оптимизировать конкатенацию строк, не требуя дополнительных изменений в байт-коде, генерируемом javac». Интересно, что недавно было объявлено, что JEP 348 («Java Compiler Intrinsics for JDK APIs») теперь кандидат в JEP, и его целью является использование аналогичного подхода для компиляции методов String::format и Objects::hash .
Как считаете, полезная статья? Ждем ваши комментарии и приглашаем всех на день открытых дверей по курсу «Разработчик Java», который уже 25 марта проведет генеральный директор компании ОТУС — Виталий Чибриков.
Конкатенация строк в Java — когда использовать +, StringBuilder и concat [duplicate]
Когда мы должны использовать + для конкатенации строк, когда предпочтительнее StringBuilder и когда подходит для использования concat.
Я слышал, что StringBuilder предпочтительнее для конкатенации внутри циклов. Почему это так?
9 ответов
Я обычно использую StringBuilder в кодах, где производительность является проблемой. Повторная конкатенация строк внутри цикла часто является хорошим кандидатом.
Причиной предпочтения StringBuilder является то, что оба + и concat создают новый объект каждый раз, когда вы их вызываете (если аргумент правой стороны не пуст). Это может быстро добавить к большому количеству объектов, почти все из которых совершенно не нужны.
Как указывали другие, когда вы используете + несколько раз в рамках одного и того же оператора, компилятор может часто оптимизировать это для вас. Однако, по моему опыту, этот аргумент не применяется, когда конкатенации происходят в отдельных утверждениях. Это, конечно, не помогает с циклами.
Сказав все это, я считаю, что главный приоритет должен заключаться в написании четкого кода. Для Java доступны некоторые отличные инструменты для профилирования (я использую YourKit), что позволяет легко выявлять узкие места производительности и оптимизировать только те биты, в которых это важно.
P.S. Мне никогда не приходилось использовать concat .
Современный компилятор Java преобразует ваши + операции в приложение StringBuilder. Я хочу сказать, если вы выполните str = str1 + str2 + str3 , тогда компилятор сгенерирует следующий код:
Вы можете декомпилировать код с помощью DJ или Cavaj, чтобы подтвердить это:) Итак, теперь это более важный выбор, чем преимущество в производительности для использования + или StringBuilder:)
Однако, учитывая ситуацию, когда компилятор не делает этого для вашего (если вы используете какой-либо частный Java SDK для этого, это может произойти), то, безусловно, StringBuilder — это путь, по которому вы избегаете большого количества ненужных String объектов.
Почему не рекомендуются множественные конкатенации string
Это хорошие способы. Почему?
![]()
![]()
![]()
![]()
- Используйте StringBuilder , когда вам нужно построить строку в цикле
- + отлично подходит для простой конкатенации, но ужасно для инкрементной сборки
- Конкатенация строк Java
- Учебники по Java — общие сведения
- Используйте StringBuilder — это намного эффективнее для этих последовательных конкатенаций
- Используйте StringBuilder(int capacity) , чтобы указать вероятную необходимую емкость, если есть способ ее предвидеть (используется средний размер выше, но другие методы может быть более удобным)
- Используйте параметр параметра Collection , чтобы обеспечить более эффективную структуру данных, чем Vector , который синхронизирован — плюс вызывающий имеет гораздо большую гибкость (например, нет необходимости копировать Set<String> в Vector<String> только для вызова этого метода).
- Простые случаи жесткого кода, если они вероятны (например, null , размер 0 и размер 1 выше).
- Используйте final , чтобы облегчить встраивание и оптимизацию JIT
- Загрузите размер strings , если он используется несколько раз. (например, используется 3 раза в приведенном выше коде.)
- Веревки для статьи Java — http://www.ibm.com/developerworks/java/library/j-ropes
- Веревки реализации Java — http://ahmadsoft.org/ropes/
- Веревки (Википедия) — http://en.wikipedia.org/wiki/Rope_(computer_science)
class MyTimer private final long start;
public long getElapsed () return System.currentTimeMillis () — start;
>
>public class AppDemo1 static final int N = 47500 ;
public static void main ( String args [])
Разница в «+» и StringBuffer
class MyPoint private final int x, y;
private final String cache;class MyTimer private final long start;
public long getElapsed () return System.currentTimeMillis () — start;
>
>public class AppDemo2 static final int N = 1000000 ;
public static void main ( String args []) MyPoint mp = new MyPoint ( 37 , 47 ) ;
String s1 = null ;
String s2 = null ;
String s3 = null ;
String s4 = null ;