Перейти к содержимому
Теория · Строки · урок 8 из 10

StringBuilder

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

Посмотрим, что происходит, когда строку собирают в цикле через +=:

C#
string line = "";
for (int i = 1; i <= 5; i++)
{
    line += i;
}
Console.WriteLine(line); // 12345

Результат верный, но давайте посчитаем работу. Строку изменить нельзя, поэтому каждый += создаёт новую строку и копирует в неё весь накопленный текст:

  1. Первый проход создаёт "1".
  2. Второй создаёт "12": копирует 1 и дописывает 2.
  3. Третий создаёт "123" и копирует уже два символа.
  4. Четвёртый создаёт "1234" и копирует три символа.
  5. Пятый копирует четыре символа ради одного нового.

За пять проходов программа создала пять строк длиной от 1 до 5 символов и записала в них 1 + 2 + 3 + 4 + 5 = 15 символов. Если кусков 10 000 по одному символу, += запишет в новые строки 1 + 2 + … + 10 000, то есть 50 005 000 символов. Работа растёт как квадрат числа кусков.

StringBuilder

StringBuilder — та самая доска: он хранит изменяемый текст в буфере, участке памяти, куда можно дописывать. Метод Append дописывает кусок в конец, не создавая новую строку, а ToString() в конце один раз создаёт готовую строку:

C#
using System.Text;

var sb = new StringBuilder();
for (int i = 1; i <= 5; i++)
{
    sb.Append(i);
}
string line = sb.ToString();
Console.WriteLine(line);      // 12345
Console.WriteLine(sb.Length); // 5

Давайте разберём пример по строкам:

  1. using System.Text; подключает пространство имён, в котором живёт StringBuilder. Об этом подробнее ниже.
  2. new StringBuilder() создаёт пустую доску. var здесь уместен: тип виден справа.
  3. sb.Append(i) дописывает в конец текст числа i. Старые символы остаются на месте, и копировать их не нужно.
  4. sb.ToString() создаёт из доски обычную строку "12345".
  5. sb.Length — сколько символов сейчас на доске.

Для 10 000 кусков StringBuilder запишет каждый символ один раз, а в ToString() скопирует все символы в строку ещё раз. Выходит около 20 000 записанных символов вместо 50 с лишним миллионов.

Пространство имён и директива using

Типы .NET разложены по группам с именами. Такая группа называется пространством имён: Console лежит в пространстве System, а StringBuilder — в System.Text. Программа .NET 10 сама подключает System и ещё несколько пространств, а System.Text в их число не входит.

Строка using System.Text; в самом начале файла — это директива using: она подключает пространство имён к файлу. После неё StringBuilder можно писать коротким именем.

Доску можно править

Доска позволяет не только дописывать. StringBuilder умеет ещё четыре вещи:

  • Insert вставляет текст в заданную позицию, например в начало;
  • Replace заменяет кусок прямо на доске, без новой строки;
  • sb[4] = 'C' меняет один символ, а у строки так нельзя;
  • Clear() стирает всё, и Length становится 0.
C#
using System.Text;

var sb = new StringBuilder("cat");
sb.Append('s');
sb.Insert(0, "two ");
sb[4] = 'C';
Console.WriteLine(sb.ToString()); // two Cats
sb.Replace("two", "three");
Console.WriteLine(sb.ToString()); // three Cats
sb.Clear();
Console.WriteLine(sb.Length);     // 0

Давайте разберём по строкам:

  1. new StringBuilder("cat") создаёт доску, на которой уже написано cat.
  2. Append('s') дописывает s: на доске cats.
  3. Insert(0, "two ") вставляет текст с индекса 0: two cats.
  4. sb[4] = 'C' меняет символ с индексом 4, первую букву cats: two Cats.
  5. Replace("two", "three") меняет two на three: three Cats.
  6. Clear() стирает всё, и Length становится 0.

Здесь доска и StringBuilder расходятся. Доска одного размера: когда место на ней кончается, писать дальше некуда. StringBuilder в этом случае сам добавляет ещё кусок памяти, а написанное остаётся на месте.

Когда нужен StringBuilder

Этот вопрос часто задают на собеседовании: зачем нужен StringBuilder, если есть +? Ответ из двух частей:

  • строка неизменяема, поэтому каждый += в цикле создаёт новую строку и копирует в неё весь накопленный текст;
  • StringBuilder дописывает куски в один изменяемый буфер и создаёт строку один раз, в ToString().

Для двух-трёх кусков StringBuilder не нужен. Компилятор превращает выражение a + b + c в один вызов, который создаёт одну строку, а интерполяция читается ещё лучше.

Частая ошибка

Забыли подключить пространство имён:

C#
var sb = new StringBuilder(); // ✗
sb.Append("Hi");
Console.WriteLine(sb.ToString());
TEXT
error CS0246: The type or namespace name 'StringBuilder' could not be found (are you missing a using directive or an assembly reference?)

Программа не компилируется: компилятор ищет StringBuilder в подключённых пространствах имён и не находит. В скобках сообщение само подсказывает причину: возможно, пропущена директива using. Допишите первой строкой файла using System.Text;.

Главное

  • Каждый += со строкой создаёт новую строку и копирует весь текст, поэтому работа цикла с += растёт как квадрат числа кусков.
  • StringBuilder хранит изменяемый текст: Append дописывает, Insert, Replace и sb[i] правят на месте, а ToString() создаёт строку.
  • StringBuilder лежит в пространстве имён System.Text, и файл начинают с using System.Text;.
  • Для нескольких кусков хватает + или интерполяции, а StringBuilder берут для сборки в цикле.