Вопрос

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

я бы ожидал Строка.Формат чтобы быть медленнее - он должен анализировать строку и затем объединить его.

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

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

Хотя бы потому, что 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);

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

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

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

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

А вот фактический код некоторых методов 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 cat = "cat";
string s = "The " + cat + " in the hat";

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

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

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

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

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

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

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

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top