Является ли String.Format столь же эффективным, как StringBuilder? - PullRequest
152 голосов
/ 09 августа 2008

Предположим, у меня есть строитель строк в 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 ]

141 голосов
/ 09 августа 2008

ПРИМЕЧАНИЕ: Этот ответ был написан, когда .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, это способствует удобочитаемости кода. Если вы подозреваете узкое место перфорации, прикрепите профилировщик к своему коду и посмотрите, где он на самом деле.

44 голосов
/ 09 августа 2008

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

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

12 голосов
/ 09 августа 2008

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

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

10 голосов
/ 09 августа 2008

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

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

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

Хотя бы потому, что 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 голосов
/ 09 августа 2008

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

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

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

5 голосов
/ 24 мая 2009

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

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

3 голосов
/ 09 августа 2008

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

string cat = "cat";
string s = "The " + cat + " in the hat";
0 голосов
/ 01 августа 2010

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

0 голосов
/ 09 августа 2008

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

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

...