Содержание
Как известно, в C# строки относятся к неизменяемым типам данных, то есть, когда мы производим над переменной типа string какие-либо операции, то в памяти создается новая строка, затем в новую строку копируются символы из старой строки и только потом старая строка удаляется из памяти. Таким образом, производить большое количество изменений строки становится слишком накладным, как в плане использования памяти, так и производительности приложения, в принципе. Однако, используя класс C# StringBuilder мы можем избежать описанных выше проблем.
Класс StringBuilder
StringBuilder — это класс для работы со строками. Расположен этот класс в пространстве имен System.Text. В отличие от String, класс StringBuilder представляет изменяемую последовательность символов и, следовательно, позволяющий более рационально использовать память.
У класса StringBuilder определены следующие методы:
Append |
Добавляет строковое представление указанного значения к экземпляру StringBuilder. Используя этот метод, можно без явных преобразований добавлять к строке числа, массивы символов, логические значения, объекты и т.д. |
Append |
Добавляет к экземпляру StringBuilder строку, возвращаемую в результате форматирования исходной строки. |
Append |
Сцепляет строковые представления элементов из указанного массива объектов, помещая между ними заданный символьный разделитель, а затем добавляет результат к текущему экземпляру StringBuilder. Имеется ряд перегруженных версий этого метода, позволяющих предоставлять в качестве второго параметра ряд массивов других типов значений. |
Append |
Добавляет символ переноса строки по умолчанию в конец текущего объекта StringBuilder. |
Clear |
Удаляет все символы из текущего экземпляра StringBuilder. |
Copy |
Копирует символы из указанного сегмента этого экземпляра StringBuilder в указанный массив Char. |
Insert |
Вставляет строковое представление указанного типа в StringBuilder в указанную позицию. |
Remove |
Удаляет указанный диапазон символов из данного экземпляра. |
Replace |
Замещает все вхождения указанного символа в данном экземпляре на другой указанный знак. |
To |
Преобразует значение данного экземпляра в String. |
Рассмотрим пример использования StringBuilder для работы со строками:
using System.Text;
namespace SBuilder
{
internal class Program
{
static void Main(string[] args)
{
StringBuilder stringBuilder= new StringBuilder();//создали экземпляр
stringBuilder.AppendLine("Hello world"); //добавили строку
stringBuilder.AppendLine("Этот текст будет на второй строке"); //добавили вторую строку
//далее будет одна строка
stringBuilder.Append("Сегодня: ");
stringBuilder.AppendFormat("{0} время {1}", DateOnly.FromDateTime(DateTime.Now), TimeOnly.FromDateTime(DateTime.Now));
Console.WriteLine(stringBuilder.ToString());
}
}
}
Результат вывода в консоли будет следующий:
Этот текст будет на второй строке
Сегодня: 19.11.2022 время 16:22
Здесь мы, используя StringBuilder создали строку и вывели её в консоль. При этом, в примере явно отсутствует ответ на вопрос — в чем наглядное преимущество использования этого класса по сравнению с обычным string? Чтобы ответить на этот вопрос, измерим время выполнения большого количества операций объединения строк, используя string и StringBuilder.
Производительность StringBuilder
Так как на время выполнения той или иной операции оказывает влияние в том числе и окружение в котором происходит тестирование, то указанные ниже результаты можно считать ориентировочными.
Метод Append
Засекать время операции будем от начала создания объекта и до окончания операций объединения строк:
static void Main(string[] args)
{
int Stringlength = 10; //итоговая длина строки
int repeatCount = 100;//количество повторений создания строки
Stopwatch stopwatch = new();
long total = 0;
for (int k = 0; k < repeatCount; k++)
{
stopwatch.Restart();
string str = "";
for (int i = 0; i < Stringlength; i++)
{
str += "0";
}
stopwatch.Stop();
total += stopwatch.Elapsed.Ticks;
}
Console.WriteLine($"Среднее время {total/ repeatCount}");
total= 0;
for (int k = 0; k < repeatCount; k++)
{
stopwatch.Restart();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < Stringlength; i++)
{
builder.Append("0");
}
stopwatch.Stop();
total += stopwatch.Elapsed.Ticks;
}
Console.WriteLine($"Среднее время {total / repeatCount}");
}
}
}
Результаты
| Длина строки | string | StringBuilder |
| 10 | 4 | 2 |
| 100 | 86 | 11 |
| 1000 | 2420 | 51 |
| 10000 | 172250 | 697 |
| 100000 | 16507314 | 5861 |
Метод Insert
Здесь будем вставлять один символ в начало уже созданной ранее строки
| Длина строки | string | StringBuilder |
| 10 | 5 | 5 |
| 100 | 87 | 39 |
| 1000 | 2426 | 1401 |
| 10000 | 175578 | 123306 |
| 100000 | 16321564 | 14974280 |
Как можно видеть из результатов, StringBuilder значительно ускоряет работу с конкатенацией строк, хотя при выполнении операции вставки подстроки в строку выигрыш и не столь очевидный. Но, опять же оговорюсь — многое в тестах зависит и от окружения в котором эти тесты выполнялись и, возможно, что у вас результаты будут выглядеть несколько иначе.
Свойство Capacity
Длина строки в StringBuilder, как и у string хранится в свойстве Length. При этом, у StringBuilder имеется ещё одно полезное свойство — Capacity, которое возвращает или задает максимальное число знаков, которое может содержаться в памяти. По умолчанию, при каждой операции в StringBuilder происходит выделение памяти под строку, что несколько замедляет работу. Если же нам изначально известна конечная длина строки, которую мы должны получить, то создав экземпляр StringBuilder с заранее заданным свойством Capacity мы можем повысить производительность. Чтобы задать значение Capacity при создании экземпляра StringBuilder необходимо передать значение в конструктор:
Capacity = 1000; StringBuilder builder = new StringBuilder(Capacity);
Например, создание строки длиной 10 000 символов методом Append без использования значения свойства Capacity в конструкторе у меня заняло время равное 6440 тактам, а при использовании — 5677.
Когда использовать StringBuilder
В вопросе типа «string vs String Builder» следует придерживаться рекомендаций разработчиков из Microsoft, а именно StringBuilder рекомендуется использовать в следующих случаях:
- Если предполагается, что код будет вносить неизвестное количество изменений в строку во время разработки (например, при использовании цикла для сцепления случайного числа строк, содержащих данные, введенные пользователем).
- Если предполагается, что код вносит значительное количество изменений в строку.
Также, можно отметить, что StringBuilder не содержит методов поиска подстрок в строке, поэтому, при выборе между string и StringBuilder стоит обратить внимание и на то, предполагается ли производить в строке большое количество операций поиска. Если да, то, возможно, стоит отказаться от StringBuilder или же производить необходимые операции поиска перед добавлением строки в StringBuilder.
Итого
Класс StringBuilder предоставляет методы для работы со строками в C#. При этом, StringBuilder может значительно повысить производительность приложения, если предполагается использование большого количества операций объединения строк. Наличие большого количества перегруженных методов у StringBuilder позволяет без дополнительных преобразований производить конкатенацию строк, используя различные типы данных.