Когда использовать StringBuilder?

83

Я понимаю преимущества StringBuilder.

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

В какой момент (количество строк) лучше использовать StringBuilder?

Шираз Бхайджи
источник
1
Я считаю, что это уже было описано ранее.
Марк Шультайс,
возможный дубликат stackoverflow.com/questions/529999/when-to-use-stringbuilder
Йорн Скоу-Роде

Ответы:

79

Я настоятельно рекомендую вам прочитать «Печальная трагедия театра микрооптимизации» Джеффа Этвуда.

Он рассматривает простую конкатенацию, StringBuilder и другие методы.

Теперь, если вы хотите увидеть какие-то числа и графики, перейдите по ссылке;)

Алекс Баньолини
источник
+1 за да ! Время, потраченное на беспокойство по этому поводу, - это время, потраченное не на то, что действительно может иметь значение.
Грег Д.
8
Однако ваше прочтение неверно: во многих случаях это не имеет значения, когда нет цикла, в других случаях, однако, это может иметь значение ОЧЕНЬ
Питер
1
Я удалил правку, потому что в принятом ответе была неверная информация.
Питер
2
и просто чтобы показать, насколько это важно, из статьи, на которую вы ссылаетесь: «В большинстве языков со сборкой мусора строки неизменяемы: когда вы добавляете две строки, содержимое обеих копируется. По мере того, как вы продолжаете добавлять, в результате получается этот цикл , с каждым разом выделяется все больше и больше памяти. Это напрямую приводит к ужасной производительности quadradic n2 »
Питер
2
Почему это принятый ответ. Я не думаю, что просто сбросить ссылку и сказать «иди, прочти это» - хороший ответ
Каньон Колоб
45

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

Это действительно так, вы можете найти, почему именно очень хорошо объяснено на:

http://www.yoda.arachsys.com/csharp/stringbuilder.html

Подводя итог: если вы можете объединить строки за один раз, как

var result = a + " " + b  + " " + c + ..

вам лучше без StringBuilder, поскольку делается только копия (длина результирующей строки рассчитывается заранее);

Для структуры типа

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

новые объекты создаются каждый раз, поэтому вам следует рассмотреть StringBuilder.

В конце статьи резюмируются эти практические правила:

Эмпирические правила

Итак, когда следует использовать StringBuilder, а когда - операторы конкатенации строк?

  • Определенно используйте StringBuilder, когда вы объединяете в нетривиальном цикле, особенно если вы не знаете наверняка (во время компиляции), сколько итераций вы сделаете через цикл. Например, чтение файла по символу за раз, построение строки по мере использования оператора + = потенциально самоубийство производительности.

  • Определенно используйте оператор конкатенации, когда вы можете (читаемо) указать все, что нужно объединить, в одном операторе. (Если у вас есть массив объектов для объединения, рассмотрите возможность явного вызова String.Concat - или String.Join, если вам нужен разделитель.)

  • Не бойтесь разбивать литералы на несколько сцепленных битов - результат будет тот же. Вы можете улучшить читаемость, например, разбив длинный литерал на несколько строк без ущерба для производительности.

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

  • Если вам нужно сделать всего несколько конкатенаций, и вы действительно хотите сделать их отдельными операторами, на самом деле не имеет значения, каким путем вы пойдете. Какой способ более эффективен, будет зависеть от количества конкатенаций, размеров задействованных строк и порядка их конкатенации. Если вы действительно считаете, что этот фрагмент кода является узким местом производительности, профилируйте или проведите сравнительный анализ в обоих направлениях.

Питер
источник
14

System.String - неизменяемый объект - это означает, что всякий раз, когда вы изменяете его содержимое, он выделяет новую строку, а это требует времени (и памяти?). Используя StringBuilder, вы изменяете фактическое содержимое объекта без выделения нового.

Поэтому используйте StringBuilder, когда вам нужно сделать много изменений в строке.

Виктор Хурдугач
источник
8

Не совсем ... вам следует использовать StringBuilder, если вы объединяете большие строки или у вас много конкатенаций, например, в цикле.

Бобби
источник
1
Это не правильно. Вы должны использовать, StringBuilderтолько если цикл или конкатенация являются проблемой производительности для спецификаций.
Alex Bagnolini
2
@Alex: Разве это не всегда так? ;) Нет, серьезно, я всегда использовал StringBuilder для конкатенации внутри цикла ... хотя все мои циклы имеют более 1000 итераций ... @Binary: Обычно это должно быть скомпилировано string s = "abcd", по крайней мере, это последнее Я слышал ... правда, с переменными, скорее всего, Concat.
Бобби,
1
Дело в том, что это почти ВСЕГДА НЕ ТАКОЕ. Я всегда использовал строковые операторы, a + "hello" + "somethingelse"и мне никогда не приходилось об этом беспокоиться. Если это станет проблемой, я воспользуюсь StringBuilder. Но я не беспокоился об этом в первую очередь и тратил меньше времени на его написание.
Alex Bagnolini
3
Нет абсолютно никакого выигрыша в производительности с большими строками - только с большим количеством конкатенаций.
Конрад Рудольф
1
@Konrad: Вы уверены, что нет увеличения производительности? Каждый раз, когда вы объединяете большие строки, вы копируете большой объем данных; Каждый раз, когда вы объединяете небольшие строки, вы копируете только небольшой объем данных.
LukeH 01
6
  • Если вы объединяете строки в цикл, вам следует рассмотреть возможность использования StringBuilder вместо обычного String.
  • В случае одиночной конкатенации вы можете вообще не увидеть разницы во времени выполнения

Вот простое тестовое приложение, чтобы доказать это:

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

Полученные результаты:

  • 30'000 итераций

    • Строка - 2000 мс
    • StringBuilder - 4 мс
  • 1000 итераций

    • Строка - 2 мс
    • StringBuilder - 1 мс
  • 500 итераций

    • Строка - 0 мс
    • StringBuilder - 0 мс
adrian.krzysztofek
источник
5

Перефразировать

Затем посчитай до трех, не больше и не меньше. Ты должен сосчитать три, и число будет три. Четыре не считай, и два не считай, за исключением того, что тогда перейди к трем. Как только число три, являющееся третьим числом, будет достигнуто, ты протолкни свою Священную ручную гранату Антиохии.

Обычно я использую построитель строк для любого блока кода, который приведет к объединению трех или более строк.

Рассел Стин
источник
Это зависит: Concetanation создает только одну копию: "Russell" + "" + Steen + ".", Будет делать только одну копию, потому что она заранее вычисляет длину строки. Только когда вам нужно разделить конкатенацию, вы должны начать думать о строителе
Питер
4

Однозначного ответа нет, только практические правила. Мои личные правила выглядят примерно так:

  • При объединении в цикл всегда используйте StringBuilder.
  • Если струны большие, всегда используйте расширение StringBuilder.
  • Если код конкатенации аккуратный и читаемый на экране, то, вероятно, все в порядке.
    Если это не так, используйте файл StringBuilder.
Лука
источник
Я знаю, что это старая тема, но я знаю только обучение и хотел знать, что вы считаете «большой строкой»?
MatthewD
4

Но если я хочу объединить две строки, я предполагаю, что лучше и быстрее сделать это без StringBuilder. Это верно?

Да. Но, что более важно, в таких ситуациях гораздо удобнее использовать ваниль String. С другой стороны, использование его в цикле имеет смысл и может быть таким же читаемым, как и конкатенация.

Я бы опасался практических правил, в которых в качестве порогового значения указывается конкретное число конкатенации. Использование его в циклах (и только в циклах), вероятно, так же полезно, легче запоминается и имеет больше смысла.

Конрад Рудольф
источник
«Я бы опасался практических правил, в которых в качестве порогового значения указывается конкретное количество конкатенации» <this. Кроме того, после применения здравого смысла подумайте о человеке, который вернется к вашему коду через 6 месяцев.
Фил Купер
4

Так как этому трудно найти объяснение, которое бы не зависело от мнений и не сопровождалось битвой гордости, я решил написать небольшой код на LINQpad, чтобы проверить это сам.

Я обнаружил, что использование строк небольшого размера вместо использования i.ToString () изменяет время отклика (видимое в небольших циклах).

В тесте используются разные последовательности итераций, чтобы измерения времени находились в разумно сопоставимых диапазонах.

Я скопирую код в конце, чтобы вы могли попробовать это сами (results.Charts ... Dump () не будет работать вне LINQPad).

Выходные данные (ось X: количество протестированных итераций, ось Y: время в тиках):

Последовательность итераций: 2, 3, 4, 5, 6, 7, 8, 9, 10 Последовательность итераций: 2, 3, 4, 5, 6, 7, 8, 9, 10

Последовательность итераций: 10, 20, 30, 40, 50, 60, 70, 80 Последовательность итераций: 10, 20, 30, 40, 50, 60, 70, 80

Последовательность итераций: 100, 200, 300, 400, 500 Последовательность итераций: 100, 200, 300, 400, 500

Код (написан с использованием LINQPad 5):

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();

    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();

    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 

    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;

        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }

        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }

        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;

    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);

        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }

    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}
Менол
источник
3

Пока вы можете физически набрать количество конкатенаций (a + b + c ...), это не должно иметь большого значения. N в квадрате (при N = 10) - это 100-кратное замедление, что не должно быть так уж плохо.

Большая проблема - когда вы объединяете сотни строк. При N = 100 вы получаете замедление в 10000 раз. Что очень плохо.

туманный
источник
2

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

Для меня я не буду использовать StringBuilder, если просто объединю две огромные строки. Если есть цикл с неопределенным счетчиком, я, скорее всего, это сделаю, даже если цикл может быть небольшим.

хорошо
источник
Действительно, было бы совершенно неправильно использовать StringBuilder для объединения двух строк, но это не имеет ничего общего с perf. тестирование - то есть просто использовать его не по назначению.
Марк Грейвелл
1

Однократное объединение не стоит использовать StringBuilder. Как правило, я использовал 5 конкатенаций.

Мэтт Врок
источник