Какой самый эффективный способ конкатенации строк

от admin

5 способов конкатенировать строки в Python 3

Конкатенация строк — операция, «склеивающая» несколько строк в одну. Это нельзя назвать особенностью языка, поскольку она присутствует и в PHP, и в Java и много где еще. Для сегодняшнего топа я собрал все способы конкатенации, кроме самых нелепых. Представляю вашему вниманию 5 способов конкатенации строк в Python 3. Сегодня мы рассмотрим варианты множественной конкатенации с применением соединительной строки.

Начнем с проверенной классики — оператора сложения для последовательной конкатенации. Думаю, всем известно, как это работает. Недостаток данного способа — в функции должно быть фиксированное число строк. Вы должны точно знать, сколько в списке строк. Я надумал 2 варианта реализации. Первый — с передачей в аргументы функции всех строк через запятую:

Второй — со списком строк в аргументах:

Здесь мы используем строковый метод join(), выполняющий конкатенацию с использованием соединительной строки. Это самое короткий и логичный ответ на такой случай:

Если нам не известно количество строк в списке, и почему-то мы не используем метод join() (не могу себе представить такую ситуацию), то этот вариант для нас. Он аналогичен работе метода join()

Как это работает? Присваиваем переменной результата значение первой строки из списка и поочередно конкатенируем к нему соединительный символ и следующие в списке строки, пока не закончится список:

Давайте вспомним, что с версии Python 2.6 существует метод format(), предоставляющий возможности форматирования строк. Строки из его аргументов подставляются в исходную строку вместо <>. Поставив рядом две и более пары фигурных скобок, можно соединить 2 и более строк. Аргументы могут быть по умолчанию, а могут быть нумерованными или именованными.

Я написал 2 варианта программы с использованием аргументов по умолчанию и нумерованных:

А здесь напомню про форматирование строк без метода format(), позаимствованное из C (это я прочитал на форуме). Работает оно точно так же, как и встроенный метод, но не позволяет передавать нумерованные и именованные аргументы. В общем вот:

Примечание: кроме всех указанных способов, я что-то написал. и сам не до конца понял, что я написал. Затем я понял, что накодил чушь. Я проверил, заранее говорю — этот способ самый медленный из всех, поэтому я решил не включать его в основной топ, но все же, вот он:

Для начала — как измерить время работы программы? Об этом я расскажу в следующей статье. Ну а пока что измерим время работы данного куска кода, где i — одна из шести функций (conc1_2, conc2, conc3, conc4_1, conc4_2, conc5):

Эффективная конкатенация строк в .NET


Для программистов на платформе .NET одним из первых советов, направленных на повышение производительности их программ, является «Используй StringBuilder для конкатенации строк». Как и «Использование исключений затратно», утверждение о конкатенации часто неправильно понимается и превращается в догму. К счастью, оно не столь деструктивно, как миф о производительности исключений, но встречается заметно чаще.

Было бы неплохо, если бы вы перед прочтением данной статьи прочли мою предыдущую статью о строках в .NET. И, во имя удобочитаемости, дальше я буду обозначать строки в .NET просто строками, а не «string» или «System.String».

Я включил эту статью в список статей, посвящённых .NET Framework в общем, а не в список C#-специфичных статей, так как полагаю, что все языки на платформе .NET под капотом содержат один и тот же механизм конкатенации строк.

Проблема, которую пытаются решить

Проблема конкатенации большого массива строк, при которой результирующая строка очень быстро и сильно растёт, очень реальна, и совет использовать StringBuilder для конкатенации очень правильный. Вот пример:

На моём относительно быстром ноутбуке выполнение данной программы заняло около 10 секунд. Если удвоить количество итераций, то время выполнения возрастёт до минуты. На .NET 2.0 beta 2 результаты несколько лучше, но не так уж и сильно. Проблема низкой производительности в том, что строки неизменяемы (immutable), и поэтому при применении оператора « += » строка на следующей итерации не добавляется в конец первой. На самом деле выражение x += «!»; абсолютно эквивалентно выражению x = x+»!»; . Здесь конкатенация — это создание полностью новой строки, для которой выделяется нужный объём памяти, в который копируется содержимое существующего значения x , а потом копируется содержимое конкатенируемой строки ( «!» ). По мере того, как результирующая строка растёт, возрастает и количество данных, которые всё время копируются туда-сюда, и именно поэтому когда я увеличил количество итераций вдвое, время выросло больше, чем в два раза.

Данный алгоритм конкатенации определённо неэффективен. Ведь если кто-то попросит вас добавить что-то в список покупок, вы же не будете перед добавлением копировать весь список, правда? Вот так мы и подходим к StringBuilder.

Используем StringBuilder

А вот эквивалент (эквивалент в смысле идентичного конечного значения x ) вышеприведённой программы, который намного-намного быстрее:

На моём ноутбуке данный код выполняется настолько быстро, что тот механизм замера времени, который я использую, неэффективен и не даёт удовлетворительных результатов. При увеличении количества итераций до одного миллиона (т.е. в 10 раз больше от изначального количества, при котором первая версия программы выполнялась 10 секунд) время выполнения вырастает до 30-40 миллисекунд. Причём время выполнения растёт приблизительно линейно количеству итераций (т.е. удвоив количество итераций, время выполнения также удвоится). Такой скачок производительности достигается благодаря устранению ненужной операции копирования — копируются только те данные, которые присоединяются к результирующей строке. StringBuilder содержит и обслуживает свой внутренний буфер и при добавлении строки копирует её содержимое в буфер. Когда новые присоединяемые строки не вмещаются в буфер, он копируется со всем своим содержимым, но уже с большим размером. По сути, внутренний буфер StringBuilder — это та же самая обычная строка; строки неизменяемы лишь с точки зрения своих публичных интерфейсов, но изменяемы со стороны сборки mscorlib . Можно было бы сделать данный код ещё более производительным, указав конечный размер (длину) строки (ведь в данном случае мы можем вычислить размер строки ещё до начала конкатенации) в конструкторе StringBuilder, благодаря чему внутренний буфер StringBuilder’а был бы создан с точно подходящим для результирующей строки размером, и в процессе конкатенации ему бы не прошлось увеличиваться через копирование. В данной ситуации вы можете определить длину результирующей строки до конкатенации, но даже если и не можете, то не беда — при заполнении буфера и его копировании StringBuilder удваивает размер новой копии, поэтому заполнений и копирований буфера не будет так уж и много.

Значит, при конкатенации я должен всегда использовать StringBuilder?

Кратко говоря — нет. Всё вышеприведённое разъясняет, почему утверждение «Используй StringBuilder для конкатенации строк» в некоторых ситуациях бывает правильным. Вместе с тем, некоторые люди принимают данное утверждение за догму, не разобравшись в основах, и вследствие этого начинают переделывать такой код:

И всё это во имя производительности. Если взглянуть на проблему в общем, то даже если вторая версия была бы более быстрой, нежели первая, то, очевидно, она не была бы намного быстрее, ведь конкатенаций всего несколько. Смысл в использовании второй версии может быть только в случае, если данный кусок кода вызывается очень, очень большое количество раз. Ухудшение удобочитаемости кода (а я думаю, вы все согласитесь, что вторая версия намного менее удобочитаемая, нежели первая) ради микроскопической прибавки производительности — это очень плохая идея.

Более того, на самом деле вторая версия, со StringBuilder’ом, менее производительна, нежели первая, хотя и не намного. И если бы вторая версия была более легко воспринимаемой, нежели первая, то вслед за аргументацией из предыдущего абзаца я бы сказал — используйте её; но когда версия со StringBuilder’ом и менее удобочитаемая, и менее производительная, то использовать её — это просто бред.

Если предположить, что firstName и lastName являются «настоящими» переменными, а не константами (об этом будет ниже), то первая версия будет скомпилирована в вызов String.Concat, как-то так:

Метод String.Concat принимает на вход набор строк (или объектов) и «склеивает» их в одну новую строку, просто и чётко. String.Concat имеет разные перегрузки — некоторые принимают несколько строк, некоторые — несколько переменных типа Object (которые при конкатенации конвертируются в строки), а некоторые принимают массивы строк или массивы Object. Все перегрузки делают одно и то же. Перед собственно началом процесса конкатенации String.Concat считывает длины всех переданных ему строк (по крайней мере, если вы передали ему строки — если вы передали переменные типа Object , то String.Concat для каждой такой переменной создаст новую временную (промежуточную) строку и будет конкатенировать уже её). Благодаря этому на момент конкатенации String.Concat точно «знает» длину результирующей строки, благодаря чему выделяет для неё точно подходящий по размерам буфер, а поэтому нет никаких лишних операций копирования и т.д.

Сравните этот алгоритм со второй StringBuilder-версией. На момент своего создания StringBuilder не знает размер результирующей строки (и мы ему этот размер не «сказали»; а если бы и сказали, то сделали бы код ещё менее понятным), а это значит, что, скорее всего, размер стартового буфера будет превышен, и StringBuilder’у придётся его увеличивать посредством создания нового и копированием содержимого. Более того, как мы помним, StringBuilder увеличивает буфер в два раза, а это значит, что, в конечном счёте, буфер окажется намного большим, нежели того требует результирующая строка. Кроме этого, не следует забывать о накладных расходах, связанных с созданием дополнительного объекта, которого нет в первой версии (этим объектом и есть StringBuilder). Так чем же вторая версия лучше?

Конкатенация строк в Java: какой способ лучше?

По возможности избегайте использования String.format() . Когда у вас более двух переменных, это медленно и трудно читать.

Постановка задачи

Есть миллион различных способов построить строку в Java, но какой из них лучше? Есть ли лучший способ? Есть четыре основных критерия для оценки наилучшего способа построения String :

  • Читаемость: нам нужно бегло просматривать код и быстро распознавать, что создается.
  • Производительность: мы хотим иметь возможность либо построить несколько строк с прямым соединением, либо очень быстро объединить множество строк в цикл.
  • Безопасность со значением NULL: мы не хотим, чтобы везде проверяли наличие NULL, поскольку это чревато ошибками.
  • Объединение объектов: в большинстве случаев, если мы сериализуем объекты вместе в строки, нам нужно, чтобы метод toString() вызывал автоматически для каждого из объектов.

При построении строки мы рассмотрим два распространенных сценария:

  1. У вас есть множество объектов и примитивов, хранящихся в отдельных локальных переменных, которые вы хотите объединить в строку.
  2. Вы пытаетесь построить строку из коллекции или массива объектов или примитивов, которые хотите объединить в строку. Я буду называть это «массовой операцией».

В этом упражнении я сосредоточусь на следующих стратегиях объединения строк:

  • Оператор +
  • StringBuilder
  • StringBuffer
  • String.concat()
  • String.format()
  • Потоки
  • String.join()
  • StringUtils Apache Commons
  • Joiner Google Guava

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

Читаемость

Я полностью понимаю, что удобочитаемость субъективна. В результате я старался говорить в общих чертах и ​​фиксировать мнения, отличные от моих.

Сценарий 1. Несколько локальных переменных

Интерполяция строк в основном считается наиболее читаемым методом построения строк из отдельных переменных. При интерполяции строк нет необходимости открывать и закрывать строки несколько раз, чтобы объединить их в одну переменную. Например, строковую интерполяцию в JavaScript очень легко понять, где переменные вставляются в строку:

Читать:
Что обозначает в отношении ct c v

К сожалению, в Java нет такого метода конкатенации строк. Вместо этого у нас остались следующие способы комбинирования строк:

StringBuilder , StringBuffer и String.concat() очень многословны и, следовательно, довольно трудны для чтения; у каждого из них есть имя метода, разделяющего каждую подстроку, что усложняет восприятие, даже если оно разбито построчно.

Использование оператора + обычно довольно легко читается с выделением синтаксиса, поскольку каждый аргумент разбивается только на один символ. Точно так же String.join() , StringUtils Apache Commons и Joiner Google Guava довольно просто читать, потому что подстроки разделяются только запятыми. При этом в данном случае String.join() и Joiner Google Guava немного неудобны, потому что между каждой из подстрок нет согласованной разделяющей строки / символа; если бы там был разделитель, их было бы очень легко прочитать.

String.format() упрощает определение того, как должна выглядеть отображаемая строка в целом, поскольку все это одна непрерывная строка с заполнителями для вводимых переменных. Однако с каждой дополнительной переменной становится сложно определить, как совпадают переменные и заполнители. В зависимости от контекста и количества переменных, String.format() может быть либо очень легко читать, либо чрезвычайно трудно читать. Когда есть несколько переменных, вы можете отформатировать его по-другому, чтобы сделать его более читаемым:

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

В этом случае потоки очень неудобны, потому что вам нужно явно создать список, преобразовать его в строку, а затем присоединиться к нему. Это очень громоздко.

Сценарий 2: сбор или массив объектов (массовая операция)

Комбинирование коллекции / массива объектов радикально отличается от конкатенации отдельных переменных с точки зрения удобочитаемости. String.join() , StringUtils Apache Commons и Joiner Google Guava — все это массовые операции, в значительной степени построенные на этом варианте использования; для выполнения операции требуется одна строка. Еще удобнее, если между элементами есть разделитель.

Потоки очень легко читать из-за их функционального стиля; если ваши объекты требуют некоторого преобразования перед объединением, определенно используйте потоки. Добавление разделителей при объединении строк также тривиально; вы просто добавляете разделитель в качестве аргумента к Collectors.joining() .

Для всех других операций требуется цикл for для построения результирующей строки, что делает ее незначительно более трудной для чтения. String.format() в этом случае особенно неудобен, потому что вам каждый раз приходится соединять две строки вместе.

Представление

Чтобы проанализировать производительность, я провел микробенчмаркинг на примерах, подобных тем, которые приведены в разделе «Удобочитаемость» выше. Ссылки на исходный код можно найти в разделе «Ссылки» ниже.

Примечание. Очевидно, что производительность зависит от используемой вами версии Java и вашего оборудования. Однако это должно дать приблизительный показатель производительности на Java 8. Для этого упражнения я использовал Java 1.8.0_152.

Сценарий 1. Несколько локальных переменных

Как вы можете видеть на графиках ниже, очевидно, что массовые операции (которые требуют создания массива или коллекции явно или скрыто) выполнялись хуже, чем отдельные. Оператор + показал себя на удивление хорошо; это, вероятно, результат преобразования компилятором простых + операций для использования StringBuilder вместо фактического создания группы временных промежуточных строк. Однако String.format() , хотя и не предназначен для массовых операций, по-прежнему работает ужасно.

Сценарий 2: сбор / массив объектов (массовая операция)

Как вы можете видеть на графиках ниже, очевидно, что массовые операции превзошли те, которые требовали цикла. StringBuilder и StringBuffer по-прежнему работали очень хорошо, потому что они не создали множество временных объектов String для выполнения конкатенации. String.format() показал худшие результаты.

Объединение объектов

К сожалению, String.join() , String.concat() и Java Streams требуют, чтобы ваши объекты были строками. С помощью Streams вы можете удовлетворить это, сопоставив объекты со строкой перед фазой сбора.

Оператор + требует, чтобы один из объектов был строкой; какой объект должен быть строкой, немного сбивает с толку из-за вывода типа.

Нулевая обработка

String.concat() и Joiner Google Guava выдают NullPointerException , если какая-либо из переменных равна нулю. StringUtils Apache Commons объединяет пустую строку вместо нулевых переменных. Другие методы конкатенации объединяют «null» всякий раз, когда есть пустая переменная.

Логирование с использованием SLF4J

Ведение журнала немного отличается от других вариантов использования, потому что цель ведения журнала — предоставление данных в очень полезном и легко читаемом формате для разработчиков. Типичная выписка из журнала обычно плохо отформатирована для общего пользования. Вместо этого в SLF4J это обычно выглядит примерно так:

Выровнять переменные и места их подстановки очень легко, потому что метки явно указаны в строке форматирования. Кроме того, для операторов журналирования у вас обычно есть только две-три переменных, что делает привязку переменных к строке форматирования довольно тривиальной. Хотя это очень похоже на String.format() , оно сильно отличается в том смысле, что у него нет параметров форматирования (например, %.2f для форматирования до двух знаков после запятой). В результате ведение журнала намного эффективнее, чем String.format() . Кроме того, ведение журнала таким способом не всегда создает String ; скорее, он создает String только в том случае, если указанный уровень ведения журнала имеет такой же или более высокий приоритет, чем конфигурация ведения журнала. Однако он все равно создаст массив за кулисами, если вы используете одну из перегрузок переменных аргументов.

По этим причинам подстановка переменных с использованием SLF4J гораздо более приемлема, чем String.format() .

Заключение

Есть миллион способов объединить строки. Я не думаю, что есть правильный путь, поскольку правильный путь обычно зависит от вашего варианта использования. Некоторые методы очень хороши для массовых операций, в то время как другие очень хороши для объединения отдельных переменных. Однако я считаю, что есть несколько неправильных способов:

  • По возможности избегайте использования String.format() . Это невероятно медленно. Также трудно читать, когда имеется более двух переменных. Не поймите меня неправильно: его можно эффективно использовать, например, для печати чисел двойной точности с определенным количеством десятичных знаков. Но поскольку это тип конкатенатора строк типа «я могу все», он обычно используется в некоторых местах и ​​способами, которых не должно быть. Эта проблема усугубляется, когда люди копируют примеры кода. Поскольку это универсальный конкатенатор / форматтер, заставить его делать то, что вы хотите, обычно довольно легко, даже если его не следует использовать.
  • Избегайте Joiner и String.concat() Google Guava, если только вы не уверены, что введенные вами данные не содержат null. Вы же не хотите, чтобы ваше приложение случайно выдало NullPointerException .
использованная литература

Если вы хотите поиграть с этим самостоятельно, я поместил проект здесь, а исходные результаты — здесь. Большая часть кода из этого сообщения в блоге основана на коде из этого сообщения в блоге.

Most efficient way to concatenate strings?

What’s the most efficient way to concatenate strings?

Theodor Zoulias's user avatar

18 Answers 18

Rico Mariani, the .NET Performance guru, had an article on this very subject. It’s not as simple as one might suspect. The basic advice is this:

If your pattern looks like:

x = f1(. ) + f2(. ) + f3(. ) + f4(. )

that’s one concat and it’s zippy, StringBuilder probably won’t help.

If your pattern looks like:

if (. ) x += f1(. )
if (. ) x += f2(. )
if (. ) x += f3(. )
if (. ) x += f4(. )

then you probably want StringBuilder.

Yet another article to support this claim comes from Eric Lippert where he describes the optimizations performed on one line + concatenations in a detailed manner.

The StringBuilder.Append() method is much better than using the + operator. But I’ve found that, when executing 1000 concatenations or less, String.Join() is even more efficient than StringBuilder .

The only problem with String.Join is that you have to concatenate the strings with a common delimiter.

Edit: as @ryanversaw pointed out, you can make the delimiter string.Empty .

There are 6 types of string concatenations:

  1. Using the plus ( + ) symbol.
  2. Using string.Concat() .
  3. Using string.Join() .
  4. Using string.Format() .
  5. Using string.Append() .
  6. Using StringBuilder .

In an experiment, it has been proved that string.Concat() is the best way to approach if the words are less than 1000(approximately) and if the words are more than 1000 then StringBuilder should be used.

For more information, check this site.

string.Join() vs string.Concat()

The string.Concat method here is equivalent to the string.Join method invocation with an empty separator. Appending an empty string is fast, but not doing so is even faster, so the string.Concat method would be superior here.

SiHa's user avatar

Rules of Thumb

When concatenating three dynamic string values or less, use traditional string concatenation.

When concatenating more than three dynamic string values, use StringBuilder .

When building a big string from several string literals, use either the @ string literal or the inline + operator.

Most of the time StringBuilder is your best bet, but there are cases as shown in that post that you should at least think about each situation.

If you’re operating in a loop, StringBuilder is probably the way to go; it saves you the overhead of creating new strings regularly. In code that’ll only run once, though, String.Concat is probably fine.

However, Rico Mariani (.NET optimization guru) made up a quiz in which he stated at the end that, in most cases, he recommends String.Format .

Here is the fastest method I’ve evolved over a decade for my large-scale NLP app. I have variations for IEnumerable<T> and other input types, with and without separators of different types ( Char , String ), but here I show the simple case of concatenating all strings in an array into a single string, with no separator. Latest version here is developed and unit-tested on C# 7 and .NET 4.7.

There are two keys to higher performance; the first is to pre-compute the exact total size required. This step is trivial when the input is an array as shown here. For handling IEnumerable<T> instead, it is worth first gathering the strings into a temporary array for computing that total (The array is required to avoid calling ToString() more than once per element since technically, given the possibility of side-effects, doing so could change the expected semantics of a ‘string join’ operation).

Next, given the total allocation size of the final string, the biggest boost in performance is gained by building the result string in-place. Doing this requires the (perhaps controversial) technique of temporarily suspending the immutability of a new String which is initially allocated full of zeros. Any such controversy aside, however.

. note that this is the only bulk-concatenation solution on this page which entirely avoids an extra round of allocation and copying by the String constructor.

Complete code:

I should mention that this code has a slight modification from what I use myself. In the original, I call the cpblk IL instruction from C# to do the actual copying. For simplicity and portability in the code here, I replaced that with P/Invoke memcpy instead, as you can see. For highest performance on x64 (but maybe not x86) you may want to use the cpblk method instead.

Related Posts