Самый эффективный способ объединения строк?

286

Каков наиболее эффективный способ объединения строк?

jimmij
источник
9
Я хочу сделать здесь заметное предупреждение о том, что принятый ответ является значительно неполным, поскольку в нем не рассматриваются все соответствующие случаи.
USR
@usr Действительно ... более подробную информацию о StringBuilderслучаях использования можно найти здесь .
Тамир Веред
Мой новый фаворит на C # 6 - это $ "Постоянный текст здесь {foo} и {bar}" ... это как String.Formatна стероидах. Который, с точки зрения производительности, немного медленнее на одной линии, чем +и String.Concat, но намного лучше, чем те, хотя и медленнее, чем StringBuilderна нескольких вызовах. Практически говоря, различия в производительности таковы, что, если бы мне пришлось выбирать только один способ объединения, я бы выбрал интерполяцию строк, используя $... Если два пути, то добавьте StringBuilderв свой набор инструментов. С этими двумя способами вы настроены.
u8it
String.JoinОтвет ниже не делает +справедливость и, практически говоря, плохой путь для конкатенации строк, но это удивительно высокая производительность мудрого. Ответ почему интересно. String.Concatи String.Joinмогут оба действовать на массивы, но String.Joinна самом деле быстрее. По-видимому, String.Joinон довольно сложный и более оптимизированный, чем String.Concatчастично, потому что он работает аналогично тому, StringBuilderчто сначала вычисляет длину строки, а затем строит строку, извлекая пользу из этого знания, используя UnSafeCharBuffer.
u8it
Итак, это быстро, но String.Joinтакже требует создания массива, который кажется неэффективным с точки зрения ресурсов, верно? ... Получается, +и String.Concatв любом случае строит массивы для их составляющих. Следовательно, создание массива вручную и его подача String.Joinвыполняется сравнительно быстрее ... однако, он по- StringBuilderпрежнему превосходит String.Joinпрактически все практические способы, хотя $лишь немного медленнее и намного быстрее на длинных строках ... не говоря уже о том, что его неудобно и некрасиво использовать, String.Joinесли у вас есть создать массив для него на месте.
u8it

Ответы:

155

Этот StringBuilder.Append()метод намного лучше, чем использование +оператора. Но я обнаружил, что при выполнении 1000 конкатенаций или менее, String.Join()это даже более эффективно, чем StringBuilder.

StringBuilder sb = new StringBuilder();
sb.Append(someString);

Единственная проблема в String.Joinтом, что вам нужно объединить строки с общим разделителем.

Изменить: как указал @ryanversaw , вы можете сделать разделитель string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });
TheEmirOfGroofunkistan
источник
11
StringBuilderимеет огромную сопоставимую стоимость запуска, он эффективен только при использовании с очень большими строками или очень большим количеством конкатенаций. Это не тривиально, чтобы выяснить для любой конкретной ситуации. Если производительность является проблемой, профилирование - ваш друг (проверьте ANTS).
Авель
32
Это не так для конкатенации одной строки. Допустим, вы выполняете myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world", компилятор автоматически превращает это в вызов string.concat, который настолько эффективен, насколько это возможно. Этот ответ неправильный, есть много лучших ответов на выбор
csauve
2
Для тривиальной конкатенации строк используйте то, что когда-либо наиболее читабельно. строка a = b + c + d; почти всегда будет быстрее, чем делать это с StringBuilder, но разница, как правило, не имеет значения. Используйте StringBuilder (или другой вариант по вашему выбору) при многократном добавлении к одной и той же строке (например, создание отчета) или при работе с большими строками.
Суонни
5
Почему ты не упомянул string.Concat?
Venemo
272

Рико Мариани , гуру .NET Performance, написал статью на эту тему. Это не так просто, как можно подозревать. Основной совет заключается в следующем:

Если ваш шаблон выглядит так:

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

это один конкат, и он быстрый, StringBuilder, вероятно, не поможет.

Если ваш шаблон выглядит так:

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

тогда вы, вероятно, хотите StringBuilder.

Еще одна статья, подтверждающая это утверждение, написана Эриком Липпертом, в котором он подробно описывает оптимизацию, выполненную для +конкатенаций в одну строку .

подветренный
источник
1
Как насчет String.Format ()?
IronSlug
86

Существует 6 типов конкатенации строк:

  1. Используя +символ плюс ( ).
  2. Использование string.Concat().
  3. Использование string.Join().
  4. Использование string.Format().
  5. Использование string.Append().
  6. Использование StringBuilder.

В эксперименте было доказано, что string.Concat()это лучший способ приблизиться, если слова меньше 1000 (приблизительно) и если слова больше 1000, тогда StringBuilderследует использовать.

Для получения дополнительной информации, проверьте этот сайт .

string.Join () против string.Concat ()

Метод string.Concat здесь эквивалентен вызову метода string.Join с пустым разделителем. Добавление пустой строки - это быстро, но не делать это даже быстрее, поэтому здесь лучше использовать метод string.Concat .

Мистер Грин
источник
4
Если читать это было доказано string.Concat () или + это лучший способ. Да, я могу получить это из статьи, но это спасает меня одним кликом. Итак, + и concat компилируются в один и тот же код.
brumScouse
Я использовал эту основу, чтобы попытаться сделать мой метод более эффективным, где мне нужно было только объединить ровно 3 строки. Я обнаружил, что на +самом деле это было на 3 миллисекунды быстрее string.Concat(), хотя я не изучал количество строк, необходимых перед string.Concat()расправой +.
Гнемлок
59

От Chinh Do - StringBuilder не всегда быстрее :

Эмпирические правила

  • При объединении трех динамических строковых значений или менее используйте традиционную конкатенацию строк.

  • При объединении более трех динамических строковых значений используйте StringBuilder.

  • При построении большой строки из нескольких строковых литералов используйте @строковый литерал или оператор inline +.

Большую часть времени StringBuilderэто ваша лучшая ставка, но в этом посте, как показано в некоторых случаях, вы должны хотя бы подумать о каждой ситуации.

бледная лошадь
источник
8
afaik @ отключает только обработку escape-последовательностей. msdn.microsoft.com/en-us/library/362314fe.aspx согласен
абатищев
12

Если вы работаете в цикле, StringBuilderвероятно, это путь; это избавляет вас от необходимости регулярно создавать новые строки. В коде, который будет запускаться только один раз, возможно, String.Concatэто нормально.

Однако Рико Мариани (гуру оптимизации .NET) составил викторину, в которой он в конце заявил, что в большинстве случаев он рекомендует String.Format.

Адам V
источник
Я много лет рекомендовал использовать string.format вместо string + string людям, с которыми работал. Я думаю, что преимущества читабельности являются дополнительным преимуществом помимо выигрыша в производительности.
Скотт Лоуренс
1
Это действительно правильный ответ. В настоящее время принятый ответ для StringBuilder является неправильным, поскольку в нем не упоминается добавление одной строки, для которого string.concat или + быстрее. Малоизвестный факт заключается в том, что компилятор фактически переводит + в string.concat. Кроме того, для циклов или для многострочных конкатов я использую пользовательский построитель строк, который добавляется только при вызове .ToString - для преодоления неопределенной проблемы с буфером, которая есть у
StringBuilder
2
string.Format не самый быстрый способ ни при каких обстоятельствах. Я не знаю, как придумать случай, когда это произойдет.
USR
@usr - обратите внимание, что Рико явно не говорит, что он самый быстрый , просто это его рекомендация: «Несмотря на то, что это худший результат, и мы заранее знали, что оба ваших архитектора CLR Performance Architects согласны с тем, что [string.Format] должен быть выбором по умолчанию. В крайне маловероятном случае, если это станет проблемой перфорирования, эта проблема легко решается с помощью лишь скромных локальных изменений. Обычно вы просто наживаете на хорошей поддержке. "
Адам V
@ AdamV вопрос о самом быстром способе. Я не согласен с тем, что это выбор по умолчанию, но не по причинам. Это может быть неуклюжий синтаксис. Решарпер может конвертировать туда и обратно по желанию.
USR
10

Вот самый быстрый метод, который я развил за десятилетие для моего крупномасштабного приложения НЛП. У меня есть варианты для IEnumerable<T>и других типов ввода, с и без разделителей разных типов ( Char, String), но здесь я показываю простой случай объединения всех строк в массиве в одну строку без разделителя. Последняя версия здесь разработана и протестирована на C # 7 и .NET 4.7 .

Есть два ключа для повышения производительности; Во-первых, необходимо предварительно рассчитать точный общий требуемый размер. Этот шаг является тривиальным, когда вход представляет собой массив, как показано здесь. Для обработки IEnumerable<T>вместо этого стоит сначала собрать строки во временный массив для вычисления этой общей суммы (массив требуется, чтобы избежать вызова ToString()более одного раза на элемент, так как технически, учитывая возможность побочных эффектов, это может изменить ожидаемую семантику операции 'string join').

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

... Заметьте , что это единственный в массе конкатенации решение на этой странице полностью избегает дополнительный раунд выделения и копирования со стороны Stringконструктора.

Полный код:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

Я должен отметить, что этот код имеет небольшие изменения по сравнению с тем, что я использую сам. В оригинале, я называю cpblk IL инструкции от C # для фактического копирования. Для простоты и переносимости кода здесь memcpy, как вы можете видеть , я заменил его на P / Invoke . Для достижения максимальной производительности на x64 ( но, возможно, не на x86 ) вы можете вместо этого использовать метод cpblk .

Гленн Слэйден
источник
string.Joinделает все эти вещи уже для вас. Там нет необходимости писать это самостоятельно. Он вычисляет размер последней строки, создает строку этого размера и затем записывает в базовый массив символов. У этого даже есть бонус использования читаемых имен переменных в процессе.
Serv
1
@Servy Спасибо за комментарий; действительно String.Joinможет быть эффективным. Как я уже говорил во введении, код здесь - просто простейшая иллюстрация семейства функций, которые я использую для сценариев, которые String.Joinлибо не обрабатываются (например, оптимизируются для Charразделителя), либо не обрабатывались в предыдущих версиях .NET. Я полагаю, что я не должен был выбирать это для простейшего примера, так как это случай, который String.Joinуже хорошо справляется, хотя и с «неэффективностью», вероятно, неизмеримой, обработки пустого разделителя, а именно. String.Empty,
Гленн
Конечно, в случае, если у вас нет разделителя, тогда вы должны позвонить Concat, что также делает это правильно. В любом случае вам не нужно писать код самостоятельно.
Serv
7
@Servy Я сравнил производительность String.Joinс моим кодом, используя этот тестовый комплект . Для 10 миллионов операций произвольной конкатенации, каждая из которых содержит до 100 строк размером с слово, приведенный выше код всегда на 34% быстрее, чем String.Joinв сборке x64 с .NET 4.7 . Поскольку OP явно запрашивает «самый эффективный» метод, результат предполагает, что мой ответ применим. Если это решит ваши проблемы, я приглашаю вас пересмотреть свое отрицательное мнение.
Гленн
1
Недавно я проверил это на CLR 4.7.1 с полной версией x64 и обнаружил, что он примерно в два раза быстрее, чем string.Join с выделением примерно на 25% меньше памяти ( i.imgur.com/SxIpEmL.png ) при использовании cpblk или github.com/ JonHanna / Mnemosyne
квентин-старин
6

Из этой статьи MSDN :

С созданием объекта StringBuilder связаны некоторые накладные расходы, как по времени, так и по памяти. На машине с быстрой памятью стоит использовать StringBuilder, если вы выполняете около пяти операций. Как правило, я бы сказал, что 10 или более строковых операций являются оправданием накладных расходов на любой машине, даже более медленной.

Так что, если вы доверяете MSDN, используйте StringBuilder, если вам нужно выполнить более 10 операций / конкатенаций строк - в противном случае простая конкататация строк с «+» подойдет.

JohnIdol
источник
5

Также важно указать, что вы должны использовать +оператор, если объединяете строковые литералы .

Когда вы объединяете строковые литералы или строковые константы с помощью оператора +, компилятор создает одну строку. Конкатенация во время выполнения не происходит.

Как: объединить несколько строк (Руководство по программированию в C #)

talles
источник
5

Добавляя к другим ответам, имейте в виду, что StringBuilder может указать начальный объем памяти для выделения .

Параметр емкости определяет максимальное количество символов, которое может быть сохранено в памяти, выделенной текущим экземпляром. Его значение присваивается свойству Capacity . Если количество символов, которые будут сохранены в текущем экземпляре, превышает это значение емкости , объект StringBuilder выделяет дополнительную память для их хранения.

Если емкость равна нулю, используется емкость по умолчанию для конкретной реализации.

Повторное добавление в StringBuilder, который не был предварительно выделен, может привести к большому количеству ненужных выделений, как многократное объединение обычных строк.

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

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

ДБН
источник
4

Ниже может быть еще одно альтернативное решение для объединения нескольких строк.

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

интерполяция строк

Р.П. Найнвал
источник
1
Это на самом деле удивительно хорошо, как общий метод конкатенации. Это в основном, String.Formatно более читабельно и с ним легче работать. Разметка его, это немного медленнее , чем +и String.Concatна одной линии сцеплений , но гораздо лучше , чем оба из тех , при повторных вызовах делает StringBuilderменее необходимым.
u8it
2

Наиболее эффективным является использование StringBuilder, например так:

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy: String.Concat - это хорошо, если у вас есть пара небольших вещей. Но если вы объединяете мегабайты данных, ваша программа, скорее всего, будет работать.

TheSmurf
источник
эй, какое решение для мегабайт данных?
Нил
2

Попробуйте это 2 куска кода, и вы найдете решение.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Vs

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

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

Второй код, возможно, память будет в порядке, но это займет больше времени ... намного дольше. Так что, если у вас есть приложение для большого количества пользователей и вам нужна скорость, используйте 1-й. Если у вас есть приложение для краткосрочного однопользовательского приложения, возможно, вы можете использовать оба, или второе будет более «естественным» для разработчиков.

Приветствия.

Эдуардо Масс
источник
1

Только для двух строк вы определенно не хотите использовать StringBuilder. Существует некоторый порог, выше которого накладные расходы StringBuilder меньше накладных расходов на выделение нескольких строк.

Итак, для более чем 2-3 строк используйте код DannySmurf . В противном случае просто используйте оператор +.

Ник
источник
1

System.String является неизменным. Когда мы изменяем значение строковой переменной, то новому значению выделяется новая память, а предыдущее выделение памяти освобождается. System.StringBuilder был разработан, чтобы иметь концепцию изменяемой строки, где различные операции могут выполняться без выделения отдельного места в памяти для измененной строки.

Dhibi_Mohanned
источник
1

Другое решение:

внутри цикла используйте список вместо строки.

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

это очень очень быстро.

asady
источник
0

Это будет зависеть от кода. Обычно StringBuilder более эффективен, но если вы объединяете только несколько строк и делаете все это в одной строке, оптимизация кода, скорее всего, позаботится об этом за вас. Важно также подумать о том, как выглядит код: для больших наборов StringBuilder облегчит чтение, для маленьких StringBuilder просто добавит ненужный беспорядок.

Джон Дьюис
источник