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

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

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

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

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

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

Ответов (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

Обновлено:

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

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

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

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

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

Хотя бы потому, что 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 не был предназначен для конкатенации, он был разработан для форматирования вывода различных входных данных, таких как дата.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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!";

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

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