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


83

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

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

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


1
Я считаю, что это уже было описано ранее.
Марк Шультайс,


Ответы:


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 мс

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 конкатенаций.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.