Является ли String.Format таким же эффективным, как StringBuilder

Предположим, у меня есть построитель строк на C#, который делает это:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

Было бы это так же эффективно или более эффективно, как наличие:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

Если да, то почему?

РЕДАКТИРОВАТЬ

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

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

Извините за путаницу

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

Mark Biek 23.09.2008 21:52

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

Abel 04.11.2009 15:53

в приведенном выше примере string s = "The "+cat+" in the hat"; может быть самым быстрым, если он не используется в цикле, и в этом случае самым быстрым будет StringBuilder , инициализированный вне цикла.

Surya Pratap 24.09.2016 13:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
161
3
66 597
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Я бы не предложил, поскольку String.Format не был предназначен для конкатенации, он был разработан для форматирования вывода различных входных данных, таких как дата.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);

Это действительно зависит от обстоятельств. Для небольших строк с небольшим количеством конкатенаций на самом деле быстрее просто добавить строки.

String s = "String A" + "String B";

Но для большей строки (очень-очень больших строк) более эффективно использовать StringBuilder.

Из Документация MSDN:

The performance of a concatenation operation for a String or StringBuilder object depends on how often a memory allocation occurs. A String concatenation operation always allocates memory, whereas a StringBuilder concatenation operation only allocates memory if the StringBuilder object buffer is too small to accommodate the new data. Consequently, the String class is preferable for a concatenation operation if a fixed number of String objects are concatenated. In that case, the individual concatenation operations might even be combined into a single operation by the compiler. A StringBuilder object is preferable for a concatenation operation if an arbitrary number of strings are concatenated; for example, if a loop concatenates a random number of strings of user input.

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

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

С другой стороны, если вы говорите о большом фрагменте статического текста с двумя или тремя переменными в нем, даже если он немного менее эффективен, я думаю, что ясность, которую вы получаете от string.Format, того стоит. Я использовал это ранее на этой неделе, когда мне нужно было разместить один бит динамического текста в центре 4-страничного документа. Будет легче обновить этот большой кусок текста, если он будет целым, чем обновлять три части, которые вы объединяете вместе.

Да! Используйте String.Format, когда это имеет смысл, например, когда вы форматируете строки. Используйте конкатенацию строк или StringBuilder при выполнении механической конкатенации. Всегда стремитесь выбрать метод, который сообщает о вашем намерении следующему сопровождающему.

Rob 24.05.2009 01:40
Ответ принят как подходящий

ПРИМЕЧАНИЕ: Этот ответ был написан, когда текущей версией была .NET 2.0. Это может больше не относиться к более поздним версиям.

String.Format использует StringBuilder внутри:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

Приведенный выше код является фрагментом из mscorlib, поэтому возникает вопрос: «StringBuilder.Append() быстрее, чем StringBuilder.AppendFormat()

Без тестирования производительности я бы, вероятно, сказал, что приведенный выше пример кода будет работать быстрее с .Append(). Но это предположение, попробуйте провести сравнительный анализ и / или профилировать их, чтобы получить правильное сравнение.

Этот парень, Джерри Диксон, провел несколько тестов:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Обновлено:

К сожалению, ссылка выше с тех пор умерла. Однако на Way Back Machine все еще есть копия:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

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

Одна проблема с тестами на странице Джерри Диксона заключается в том, что он никогда не вызывает .ToString() для объекта StringBuilder. На протяжении многих итераций это время имеет большое значение и означает, что он не совсем сравнивает яблоки с яблоками. Вот почему он показывает такую ​​высокую производительность для StringBuilder и, вероятно, объясняет его удивление. Я просто повторил тест, исправив эту ошибку, и получил ожидаемые результаты: оператор String+ был самым быстрым, за ним шел StringBuilder, а String.Format оказался позади.

Ben Collins 20.07.2013 01:28

6 лет спустя это уже не совсем так. В Net4 string.Format () создает и кэширует экземпляр StringBuilder, который он повторно использует, поэтому в некоторых тестовых случаях он может быть быстрее, чем StringBuilder. В ответе ниже я добавил пересмотренный тест (который по-прежнему говорит о том, что concat является самым быстрым, а для моего тестового примера формат на 10% медленнее, чем StringBuilder).

Chris F Carroll 12.01.2015 15:28

In both cases above I want to inject one or more strings into the middle of a predefined template string.

В этом случае я бы предложил String.Format - самый быстрый, потому что он предназначен именно для этой цели.

Я ожидал, что String.Format будет медленнее - он должен проанализировать строку, а тогда объединить ее.

Пара примечаний:

  • Формат - это способ использовать видимые для пользователя строки в профессиональных приложениях; это позволяет избежать ошибок локализации
  • Если вы заранее знаете длину результирующей строки, используйте конструктор StringBuilder (Int32) для предопределения емкости

Я провел несколько быстрых тестов производительности, и для 100 000 операций в среднем за 10 запусков первый метод (String Builder) занимает почти половину времени, чем второй (String Format).

Так что, если это нечасто, это не имеет значения. Но если это обычная операция, вы можете использовать первый метод.

Да еще, самым быстрым было бы:

string cat = "cat";
string s = "The " + cat + " in the hat";

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

Abel 04.11.2009 15:47

@Abel: Возможно, в ответе не хватает деталей, но этот подход ЯВЛЯЕТСЯ самым быстрым вариантом в данном конкретном примере. Компилятор преобразует это в один вызов String.Concat (), поэтому замена на StringBuilder фактически замедлит код.

Dan C. 02.12.2011 12:47

@Vaibhav верен: в этом случае конкатенация самая быстрая. Конечно, разница будет незначительной, если не будет повторяться очень много раз или, возможно, работать с гораздо большей струной.

Ben Collins 20.07.2013 01:38

String.Format внутренне использует StringBuilder, поэтому логично предположить, что он будет немного менее производительным из-за больших накладных расходов. Однако простая конкатенация строк - это самый быстрый способ в значительной степени вставить одну строку между двумя другими. Это свидетельство было продемонстрировано Рико Мариани в его самой первой викторине, много лет назад. Простой факт заключается в том, что конкатенации, когда известно количество частей строки (без ограничений - вы можете объединить тысячу частей, если вы знаете, что это всегда 1000 частей), всегда быстрее, чем StringBuilder или String.Format. Они могут выполняться с одним выделением памяти и серией копий памяти. Здесь - доказательство.

А вот фактический код некоторых методов String.Concat, которые в конечном итоге вызывают FillStringChecked, который использует указатели для копирования памяти (извлеченной через Reflector):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

Итак, тогда:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

Наслаждаться!

в Net4 string.Format кэширует и повторно использует экземпляр StringBuilder, поэтому в некоторых случаях может быть быстрее.

Chris F Carroll 12.01.2015 15:33

Это действительно зависит от вашей схемы использования. Подробный эталонный тест между string.Join, string,Concat и string.Format можно найти здесь: String.Format не подходит для интенсивного ведения журнала

Хотя бы потому, что string.Format не совсем то, что вы могли подумать, вот повторный запуск тестов на Net45 через 6 лет.

Concat по-прежнему самый быстрый, но на самом деле разница менее 30%. StringBuilder и Format различаются всего на 5-10%. У меня были вариации на 20%, проведя тесты несколько раз.

Миллисекунды, миллион итераций:

  • Конкатенация: 367
  • Новый stringBuilder для каждого ключа: 452
  • Кешированный StringBuilder: 419
  • строка.Формат: 475

Урок, который я извлекаю, заключается в том, что разница в производительности тривиальна, и поэтому она не должна мешать вам писать простейший читаемый код, который вы можете. На мои деньги это часто, но не всегда, a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);

Под "string.Format" не совсем то, что вы думаете "я имею в виду, что в исходном коде 4.5 он пытается создать и повторно использовать кэшированный экземпляр StringBuilder. Поэтому я включил этот подход в тест

Chris F Carroll 12.01.2015 20:04

Другие вопросы по теме