Является ли 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);

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

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

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

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

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

9.08.2008 14:24:05
Пожалуйста, оставьте их открытыми для будущих улучшений.
Mark Biek 23.09.2008 17:52:45
В случае особого случая самый быстрый из них - ни один из них: если заменяемая деталь по размеру равна новой детали, вы можете изменить строку на месте. К сожалению, это требует отражения или небезопасного кода и намеренно нарушает неизменность строки. Не очень хорошая практика, но если скорость это проблема ... :)
Abel 4.11.2009 12:53:59
в приведенном выше примере string s = "The "+cat+" in the hat";может быть самым быстрым, если он не используется в цикле, и в этом случае самый быстрый будет с StringBuilder инициализированным вне цикла.
Surya Pratap 24.09.2016 10:12:48
12 ОТВЕТОВ
РЕШЕНИЕ

ПРИМЕЧАНИЕ. Этот ответ был написан, когда .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

Обновлено:

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

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

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

146
7.03.2019 17:19:48
Одна из проблем , с критериями на странице Джерри Диксона является то , что он никогда не называет .ToString()на StringBuilderобъекте. На протяжении многих итераций это время имеет большое значение и означает, что он не совсем сравнивает яблоки с яблоками. Вот почему он демонстрирует такие отличные результаты StringBuilderи, вероятно, объясняет его удивление. Я просто повторил тест с исправлением этой ошибки и получил ожидаемые результаты: String +оператор был самым быстрым, а затем StringBuilder, с String.Formatзамыкающим.
Ben Collins 19.07.2013 21:28:17
6 лет спустя это уже не так. В Net4 string.Format () создает и кэширует экземпляр StringBuilder, который он использует повторно, поэтому в некоторых тестовых случаях он может быть быстрее, чем StringBuilder. Ниже приведен пересмотренный тест в ответе (который по-прежнему говорит, что concat является самым быстрым, и для моего тестового примера формат на 10% медленнее, чем StringBuilder).
Chris F Carroll 12.01.2015 12:28:50

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

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

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

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

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

0
9.08.2008 14:31:51

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

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

45
9.08.2008 14:36:45

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

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

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

8
9.08.2008 14:40:00
Да! Используйте String.Format, когда это имеет смысл, т.е. когда вы форматируете строки. Используйте конкатенацию строк или StringBuilder при выполнении механической конкатенации. Всегда старайтесь выбрать метод, который сообщает о вашем намерении следующему сопровождающему.
Rob 23.05.2009 21:40:06

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

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

0
9.08.2008 16:29:55

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

Пара заметок:

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

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

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

12
9.08.2008 19:34:51

О, также, самый быстрый будет:

string cat = "cat";
string s = "The " + cat + " in the hat";
3
9.08.2008 19:51:24
нет, конкатенация строк очень медленная, потому что .NET создает дополнительные копии строковых переменных между операциями concat, в данном случае: две дополнительные копии плюс окончательная копия для назначения. Результат: крайне низкая производительность по сравнению с StringBuilderкоторой в первую очередь оптимизируется этот тип кодирования.
Abel 4.11.2009 12:47:50
Быстрее всего печатать может быть;)
UpTheCreek 13.05.2010 09:46:59
@Abel: Возможно, в ответе не хватает деталей, но этот подход - самый быстрый вариант, в данном конкретном примере. Компилятор преобразует это в один вызов String.Concat (), поэтому замена на StringBuilder фактически замедлит код.
Dan C. 2.12.2011 08:47:08
@Vaibhav правильно: в этом случае конкатенация является самой быстрой. Конечно, разница была бы незначительной, если бы она не повторялась много раз или, возможно, не работала над гораздо более крупной струной.
Ben Collins 19.07.2013 21:38:15

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

А вот фактический код для некоторых методов 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!";

Наслаждайтесь!

6
7.02.2016 17:59:44
в Net4 string.Format кэширует и повторно использует экземпляр StringBuilder, поэтому в некоторых случаях может быть быстрее.
Chris F Carroll 12.01.2015 12:33:28

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

0
17.10.2019 12:44:45

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

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);
8
7.02.2016 18:00:17
Под «string.Format точно не делается то, что вы могли бы подумать», я имею в виду, что в исходном коде 4.5 он пытается создать и повторно использовать кэшированный экземпляр StringBuilder. Так что я включил этот подход в тест
Chris F Carroll 12.01.2015 17:04:56