Объект String является неизменяемым. Каждый раз при использовании одного из методов в классе System.String вы создаете объект строки в памяти, для которого требуется выделение нового пространства. В случаях, когда необходимо выполнять повторяющиеся изменения строки, издержки, связанные с созданием объекта String, могут оказаться значительными. Чтобы изменять строку без создания нового объекта, можно использовать класс System.Text.StringBuilder. Например, использование класса StringBuilder может повысить производительность при соединении большого количества строк в цикле.
Импорт пространства имен System.Text
Класс StringBuilder находится в пространстве имен System.Text. Чтобы не указывать в коде полное имя типа, вы можете импортировать пространство имен System.Text:
Создание экземпляров объекта StringBuilder
Вы можете создать экземпляр класса StringBuilder путем инициализации переменной с помощью одного из перегруженных методов конструктора, как показано в следующем примере.
Настройка емкости и длины
Хотя StringBuilder является динамическим объектом, который позволяет вам развернуть ряд символов в строке, которые она инкапсулирует, вы можете указать максимальное число содержащихся в ней символов. Это значение называется емкостью объекта, и его не следует путать с длиной строки, которую содержит текущий объект StringBuilder. Например, вы можете создать экземпляр класса StringBuilder со строкой Hello, длина которой составляет 5 символов, указав при этом максимальную емкость объекта 25 символов. При изменении значения StringBuilder размер не перераспределяется, если не достигнута максимальная емкость. Когда это происходит, новое пространство выделяется автоматически, а емкость удваивается. Вы можете указать емкость класса StringBuilder с помощью одного из перегруженных конструкторов. В следующем примере показано, что объект myStringBuilder можно развернуть максимум на 25 позиций.
Кроме того, вы можете использовать доступное для чтения или записи свойство Capacity, чтобы задать максимальную длину объекта. В следующем примере используется свойство Capacity для определения максимальной длины объекта.
Метод EnsureCapacity можно использовать для проверки емкости текущего объекта StringBuilder. Если емкость больше переданного значения, изменения не вносятся; однако если емкость меньше переданного значения, текущая емкость изменяется для соответствия переданному значению.
Можно также просмотреть или задать свойство Length. Если вы задали для свойства Length значение больше значения свойства Capacity, значение свойства Capacity будет автоматически изменено на то же самое значение, что и у свойства Length. Если задать для свойства Length значение меньше длины строки в текущем объекте StringBuilder, строка будет укорочена.
Изменение строки StringBuilder
В следующей таблице перечислены методы, которые можно использовать для изменения содержимого объекта StringBuilder.
| Имя метода | Использовать |
|---|---|
| StringBuilder.Append | Добавляет сведения в конец текущего объекта StringBuilder. |
| StringBuilder.AppendFormat | Заменяет отформатированным текстом описатель формата, переданный в строке. |
| StringBuilder.Insert | Вставляет строку или объект в указанный индекс текущего объекта StringBuilder. |
| StringBuilder.Remove | Удаляет указанное количество символов из текущего объекта StringBuilder. |
| StringBuilder.Replace | Замещает все вхождения указанного символа или строки в текущем StringBuilder на другой указанный символ или строку. |
Добавить
Метод Append позволяет добавить текст или строковое представление объекта к концу строки, представленной текущим объектом StringBuilder. Следующий пример инициализирует StringBuilder с текстом Hello World, а затем добавляет текст в конец объекта. Пространство выделяется автоматически при необходимости.
AppendFormat
Метод StringBuilder.AppendFormat добавляет текс в конец объекта StringBuilder. Он поддерживает возможность составного форматирования (дополнительные сведения см. в статье Составное форматирование) путем вызова реализации IFormattable для одного или нескольких форматируемых объектов. Следовательно, он принимает строки стандартного формата для числовых значений, значений даты и времени, значений перечисления, строки пользовательского формата для чисел и дат и строки формата, определенные для пользовательских типов. (Дополнительные сведения о форматировании см. в разделе Типы форматирования.) Вы можете использовать этот метод для настройки формата переменных и добавления этих значений к StringBuilder. В следующем примере метод AppendFormat помещает в конец объекта StringBuilder целочисленное значение, отформатированное в виде значения валюты.
Insert
Метод Insert добавляет строку или объект в указанную позицию в текущем объекте StringBuilder. В следующем примере этот метод вставляет слово в шестую позицию объекта StringBuilder.
Удалить
Вы можете использовать метод Remove, чтобы удалить указанное количество символов из текущего объекта StringBuilder, начиная с указанного индекса (с отсчетом с нуля). В следующем примере метод Remove используется для сокращения объекта StringBuilder.
Замените
Метод Replace можно использовать для замены символов в объекте StringBuilder другими указанными символами. В следующем примере метод Replace находит все экземпляры восклицательного знака (!) в объекте StringBuilder и заменяет их знаками вопроса (?).
StringBuilder в Java
Программы Java используют класс StringBuilder для добавления строк. Добавление строк становится все более сложным процессом по мере того, как строки становятся длиннее.
Требуется все больше и больше памяти. Программы тормозят.
Решение. Класс StringBuilder в Java – решение этой проблемы, он просто повторно использует единственный буфер. Ускоряет добавления, вставки.
Пример далее импортирует java.lang.StringBuilder. Мы создаем StringBuilder. Зацикливаем и добавляем к нему пять строк и конвертируем данные в объект String. Затем отображаем полученную строку в консоли с помощью метода System.out.println.
Часто мы используем StringBuilders в циклах. Здесь у нас часто неизвестное количество итераций. А StringBuilder оптимизирует случаи, когда происходит много вызовов append().
Следовательно использование StringBuilder в цикле может предотвратить возникновение проблемы с производительностью генерации.
В этом примере мы вызываем append() для результата предыдущего вызова append() и он возвращает исходный StringBuilder.
StringBuilder имеет изменяемый буфер. Таким образом, мы можем быстро добавлять или вставлять данные. Он получает два аргумента: индекс и значение, которое мы хотим вставить.
Индекс: это первый аргумент. Чтобы вставить после второго символа, используйте значение 2. А для вставки в начале используйте ноль.
Строка: мы передаем строку (или другое значение) в качестве второго аргумента. Это данные, которые помещаются в стрингбилдер.
Мы используем метод indexOf для поиска подстроки в данных. Если подстрока найдена, первый индекс, в котором она встречается, возвращается как int.
Замена. Этот метод принимает два индекса и заменяет символы в этом диапазоне указанной строкой. Replace в StringBuilder работает иначе, чем replace() в Strings.
Объединение. Мы можем добавить один StringBuilder к другому. Мы просто вызываем append() и передаем второй StringBuilder в качестве аргумента.
Подстрока или Substring в Java. Этот метод находится в классе AbstractStringBuilder. Он предоставляет полезную возможность извлекать диапазон символов в новую строку.
Перебор символов. Цикл for может перебирать символы. Мы обращаемся к методу length(), чтобы получить размер StringBuilder, а затем используем charAt() для доступа к символам.
CharAt: обращается к символу в StringBuilder. Мы можем использовать его где угодно, а не только в цикле for.
SetLength. Мы можем изменить длину StringBuilder с помощью setLength. Это часто бывает полезно, когда мы хотим сократить или уменьшить количество символов в объекте.
Вместимость. Внутренне StringBuilder изменяется по мере увеличения количества символов. Это медленно. Благодаря большей емкости, установленной в конструкторе, мы избегаем этих изменений размеров.
StringBuilder намного быстрее делает добавление, чем String. Для добавления String требуется значительное время(251 мс), а для добавления StringBuilder требуется менее 1 мс.
Производительность емкости. Здесь мы возвращаемся к емкости. В этом тесте размер первого StringBuilder увеличивается до миллиона символов без установленной емкости.
И еще: второй StringBuilder тем временем использует точную емкость 1000000.
Результат: точная емкость позволяет второму StringBuilder почти в два раза быстрее выполнять свою задачу.
Добавление строки вместо символа. Вот еще один тест. Он сравнивает вызов append() с односимвольным строковым литералом и char. Аргумент String работает медленнее.
Когда это возможно: быстрее использовать аргумент char для append(), чем аргумент String. Я обнаружил, что это помогает многим программам.
Массивы символов. Мы можем использовать массив символов вместо StringBuilder для создания строк. В моих тестах это быстрее, но размер массивов нельзя изменить, поэтому он менее гибкий.
StringBuffer. Это более старая версия StringBuilder. Эти два класса почти одинаковы, за исключением того, что StringBuffer является потокобезопасным и несколько медленнее.
Средняя оценка / 5. Количество голосов:
Или поделись статьей
Видим, что вы не нашли ответ на свой вопрос.
Когда использовать StringBuilder?
Я понимаю преимущества StringBuilder.
Но если я хочу конкатенировать 2 строки, то я предполагаю, что лучше (быстрее) сделать это без StringBuilder. Правильно ли это?
В какой момент (количество строк) становится лучше использовать StringBuilder?
ОТВЕТЫ
Ответ 1
Я тепло предлагаю вам прочитать The Sad Tragedy of the Micro-Optimization Theatre от Джеффа Этвуда.
Он обрабатывает Simple Concatenation vs. StringBuilder и другие методы.
Теперь, если вы хотите увидеть некоторые цифры и графики, перейдите по ссылке;)
Ответ 2
Но если я хочу объединить 2 строки, тогда я предполагаю, что это лучше (быстрее) сделать это без StringBuilder. Это правильно?
Это действительно правильно, вы можете найти, почему точно объяснил очень хорошо:
Подводя итог: если вы можете объединить строки за один раз, как
вам лучше без StringBuilder, потому что создается только копия (длина результирующей строки вычисляется заранее.);
Для структуры типа
новые объекты создаются каждый раз, поэтому вам следует рассмотреть StringBuilder.
В конце статьи приводятся общие правила:
Итак, когда вы должны использовать StringBuilder, и когда вы должны использовать строку операторы конкатенации?
Если вам нужны промежуточные результаты конкатенации чего-то кроме подачи следующей итерации конкатенации, StringBuilder не является собираюсь помочь тебе. Например, если вы создаете полное имя из первого имя и фамилию, а затем добавить третья часть информации ( прозвище, может быть) до конца только выгода от использования StringBuilder если вам не нужно (имя + фамилия) строка для других целей (как мы делаем в примере, который создает объект Person).
Если у вас есть несколько объединений делать, и вы действительно хотите сделать их в отдельных заявлениях, это не действительно важно, каким путем вы идете. Который Более эффективный способ будет зависеть от количество конкатенаций размеров строки и в каком порядке они соединены. Если вы действительно считаю, что кусок кода, чтобы быть узкое место, профиль или сравните это в обоих направлениях.
Ответ 3
Поэтому используйте StringBuilder, когда вам нужно сделать много изменений в строке.
Ответ 4
Не совсем. вы должны использовать StringBuilder, если вы объединяете большие строки или у вас много конкатенаций, например, в цикле.
Ответ 5
Вот простое тестовое приложение, чтобы доказать точку:
Результаты:
Ответ 6
Но если я хочу объединить 2 строки, то я предполагаю, что лучше и быстрее сделать это без StringBuilder. Это правильно?
Я бы с осторожностью следил за эмпирическими правилами, в которых в качестве порогового значения приводятся конкретные числа конкатенации. Использование его в циклах (и только в циклах), вероятно, столь же полезно, легче запомнить и имеет больше смысла.
Ответ 7
Нет окончательного ответа, только правила большого пальца. Мои личные правила выходят примерно так:
Ответ 8
Я обычно использую строковый построитель для любого блока кода, который приведет к конкатенации трех или более строк.
Ответ 9
Пока вы можете физически вводить количество конкатенаций (a + b + c. ), это не должно иметь большого значения. Квадрат N (при N = 10) представляет собой 100-кратное замедление, которое не должно быть слишком плохим.
Большая проблема заключается в том, что вы объединяете сотни строк. При N = 100 вы получаете замедление в 10000 раз. Это довольно плохо.
Ответ 10
Я не думаю, что существует тонкая грань между тем, когда использовать или когда этого не делать. Если, конечно, кто-то не выполнил некоторые обширные испытания, чтобы выйти с золотыми условиями.
Для меня я не буду использовать StringBuilder, если просто конкатенировать 2 огромные строки. Если цикл с неопределенным подсчетом, я, скорее всего, даже если цикл может быть небольшим.
Ответ 11
Одиночная конкатенация не стоит использовать StringBuilder. Я, как правило, использовал 5 конкатенаций.
Ответ 12
Поскольку было трудно найти объяснение этому, на которое бы не влияли мнения или не последовало сражение гордости, я решил написать на LINQpad немного кода, чтобы проверить это сам.
Я обнаружил, что использование строк небольшого размера вместо использования i.ToString() изменяет время отклика (отображается в маленьких циклах).
В тесте используются разные последовательности итераций для измерения времени в разумно сопоставимых диапазонах.
В конце я скопирую код, чтобы вы могли попробовать его самостоятельно (results.Charts. Dump() не будет работать вне LINQPad).
Выходные данные (ось Y: количество проверенных итераций, ось X: отдельные тесты):
Последовательность итераций: 2, 3, 4, 5, 6, 7, 8, 9, 10
Последовательность итераций: 10, 20, 30, 40, 50, 60, 70, 80
Последовательность итераций: 100, 200, 300, 400, 500
Код (написанный с использованием LINQPad 5):
Обработка строк в Java. Часть I: String, StringBuffer, StringBuilder
Вступление
Что вы знаете о обработке строк в Java? Как много этих знаний и насколько они углублены и актуальны? Давайте попробуем вместе со мной разобрать все вопросы, связанные с этой важной, фундаментальной и часто используемой частью языка. Наш маленький гайд будет разбит на две публикации:
String
Строка — объект, что представляет последовательность символов. Для создания и манипулирования строками Java платформа предоставляет общедоступный финальный (не может иметь подклассов) класс java.lang.String. Данный класс является неизменяемым (immutable) — созданный объект класса String не может быть изменен. Можно подумать что методы имеют право изменять этот объект, но это неверно. Методы могут только создавать и возвращать новые строки, в которых хранится результат операции. Неизменяемость строк предоставляет ряд возможностей:
Создание
Мы можем создать объект класса String несколькими способами:
1. Используя строковые литералы:
Строковый литерал — последовательность символов заключенных в двойные кавычки. Важно понимать, что всегда когда вы используете строковой литерал компилятор создает объект со значением этого литерала:
2. С помощью конструкторов:
Если копия строки не требуется явно, использование этих конструкторов нежелательно и в них нет необходимости, так как строки являются неизменными. Постоянное строительство новых объектов таким способом может привести к снижению производительности. Их лучше заменить на аналогичные инициализации с помощью строковых литералов.
Конструкторы могут формировать объект строки с помощью массива символов. Происходит копирование массива, для этого используются статические методы copyOf и copyOfRange (копирование всего массива и его части (если указаны 2-й и 3-й параметр конструктора) соответственно) класса Arrays, которые в свою очередь используют платформо-зависимую реализацию System.arraycopy.
Можно также создать объект строки с помощью массива байтов. Дополнительно можно передать параметр класса Charset, что будет отвечать за кодировку. Происходит декодирование массива с помощью указанной кодировки (если не указано — используется Charset.defaultCharset(), который зависит от кодировки операционной системы) и, далее, полученный массив символов копируется в значение объекта.
Ну и наконец-то конструкторы использующие объекты StringBuffer и StringBuilder, их значения (getValue()) и длину (length()) для создания объекта строки. С этими классами мы познакомимся чуть позже.
Приведены примеры наиболее часто используемых конструкторов класса String, на самом деле их пятнадцать (два из которых помечены как deprecated).
Длина
Важной частью каждой строки есть ее длина. Узнать ее можно обратившись к объекту String с помощью метода доступа (accessor method) length(), который возвращает количество символов в строке, например:
Конкатенация
Конкатенация — операция объединения строк, что возвращает новую строку, что есть результатом объединения второй строки с окончанием первой. Операция для объекта String может быть выполнена двумя способами:
1. Метод concat
Важно понимать, что метод concat не изменяет строку, а лишь создает новую как результат слияния текущей и переданной в качестве параметра. Да, метод возвращает новый объект String, поэтому возможны такие длинные «цепочки».
2. Перегруженные операторы «+» и «+=«
Это одни с немногих перегруженных операторов в Java — язык не позволяет перегружать операции для объектов пользовательских классов. Оператор «+» не использует метод concat, тут используется следующий механизм:
Используйте метод concat, если слияние нужно провести только один раз, для остальных случаев рекомендовано использовать или оператор «+» или StringBuffer / StringBuilder. Также стоит отметить, что получить NPE (NullPointerException), если один с операндов равен null, невозможно с помощью оператора «+» или «+=«, чего не скажешь о методе concat, например:
Форматирование
Класс String предоставляет возможность создания форматированных строк. За это отвечает статический метод format, например:
Методы
Благодаря множеству методов предоставляется возможность манипулирования строкой и ее символами. Описывать их здесь нет смысла, потому что Oracle имеет хорошие статьи о манипулировании и сравнении строк. Также у вас под рукой всегда есть их документация. Хотелось отметить новый статический метод join, который появился в Java 8. Теперь мы можем удобно объединять несколько строк в одну используя разделитель (был добавлен класс java.lang.StringJoiner, что за него отвечает), например:
Это не единственное изменение класса в Java 8. Oracle сообщает о улучшении производительности в конструкторе String(byte[], *) и методе getBytes().
Преобразование
1. Число в строку
2. Строку в число
StringBuffer
Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует драгоценную память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если Вы хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными.
Создание
Существует четыре способа создания объекта класса StringBuffer. Каждый объект имеет свою вместимость (capacity), что отвечает за длину внутреннего буфера. Если длина строки, что хранится в внутреннем буфере, не превышает размер этого буфера (capacity), то нет необходимости выделять новый массив буфера. Если же буфер переполняется — он автоматически становиться больше.
Модификация
В большинстве случаев мы используем StringBuffer для многократного выполнения операций добавления (append), вставки (insert) и удаления (delete) подстрок. Тут все очень просто, например:
Все остальные методы для работы с StringBuffer можно посмотреть в документации.
StringBuilder
StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах есть нежелательным. Следовательно, если вы работаете с многопоточностью, Вам идеально подходит StringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций. Напишем небольшой тест для сравнения скорости работы этих двух классов:
Спасибо за внимание. Надеюсь статья поможет узнать что-то новое и натолкнет на удаление всех пробелов в этих вопросах. Все дополнения, уточнения и критика приветствуются.
StringBuilder прошлое и настоящее
Вступление
Что же плохого в этом коде? А то, что на каждой итерации создается строка длиною на единицу больше чем на предыдущем шаге с последующим копированием символов из старой строки. Таким образом, суммарное количество задействованных символов равно:
Данная формула есть не что иное, как сумма арифметической прогрессии:
То есть такой сценарий конкатенации требует памяти пропорционально O(n 2 ), n — длина строки.
Для устранения проблем в подобном коде мы используем класс StringBuilder, зная, что операции с ним не приводят к таким растратам памяти как со String. Фактически StringBuilder представляет собой изменяемую строку.
Такой код хоть и не лишен всех недостатков и тратит некоторую память на ветер, но делает это более сдержанно, по сравнению с предыдущим кодом.
m_currentThread — идентификатор потока, в котором создавался экземпляр объекта;
m_MaxCapacity — максимальная емкость данного экземпляра;
m_StringValue — строка, содержащая символы.
Фактически класс StringBuilder внутри работает со строковым типом данных String. Поскольку строки со стороны mscorlib.dll являются изменяемыми, то StringBuilder-у ничего не стоит изменить строку, находящуюся в m_StringValue.
Первоначальная длина составляет 16 символов, а при нехватке места для добавления новых символов StringBuilder заменяет внутреннюю строку на строку длиною в два раза больше и копирует во вновь созданную все символы из предыдущей + новые. Удвоение длины строки приводит к линейной сложности (O(n)) по памяти, в отличие от квадратичной, которая присуща обычным строкам.
Важным дополнением к увеличению производительности является задание нужной емкости при создании экземпляра класса StringBuilder. Тем самым нам не требуется очередное удвоение размера и копирование данных при нехватке памяти. Однако, это возможно лишь в том случае, если мы заранее знаем размер получаемой строки.
Рассмотрим реализации наиболее часто используемых методов. Для читаемости кода я убрал условия проверки входных параметров.
Метод Append()
Данный метод просто проверяет, хватает ли места в текущем экземпляре для добавления новой строки, если да, то просто происходит копирование на месте в незанятую часть строки, иначе удвоение размера и копирование старой и новой строки.
Метод Insert()
Данный метод аналогично предыдущему проверяет, хватает ли места в текущем экземпляре для вставки новой строки и в зависимости от этого удваивает размер строки или же вставляет на месте в исходную строку без изменения размера.
Метод Remove()
Данный метод удаляет ненужные символы, сдвигая оставшуюся часть строки влево. При удалении последнего символа фактически ничего сдвигать не надо, поэтому удаление с конца происходит намного быстрее, чем из любой другой части строки.
Метод ToString()
Причина такого изменения достаточно очевидна: при такой реализации не требуется перевыделять память при ее нехватке, что присуще предыдущей реализации. Это так же означает, что метод ToString() работает немного медленнее, поскольку окончательную строку необходимо сначала сформировать, а метод Append() работает быстрее, поскольку не требует копирования. Однако это вписывается в типичный сценарий использования для StringBuilder: много вызовов Append(), а затем один вызов ToString().
m_ChunkChars — массив содержащий символы текущего элемента связного списка (кусочка строки);
m_ChunkPrevious — ссылка на предыдущий элемент (StringBuilder) в списке;
m_ChunkLength — реальная длина текущего элемента списка (количество используемых символов);
m_ChunkOffset — суммарное количество используемых строкой символов (логическая длина);
m_MaxCapacity — максимальная емкость текущего экземпляра StringBuilder.
Максимальная длина элемента списка MaxChunkSize равна 8000. Как вы понимаете это сделано не просто так. Вот комментарий разработчиков класса:
We want to keep chunk arrays out of large object heap ( Исходный код
Метод Append() работает следующим образом: если в текущем элементе списка хватает символов для вставки новой строки, то происходит копирование в нее, если же нет, то копируется та часть которая помещается, а для того, что не поместилось, создается новый элемент списка (экземпляр StringBuilder-a), у которого длина массива равна длине всей исходной строки либо длине оставшейся строки в зависимости от того что больше. Однако, как было сказано, выше максимальная длина массива составляет 8000.
В общем, формула для вычисления длины нового элемента списка выглядит так:
где minBlockCharCount — оставшаяся длина строки после копирования ее части которая помещается в текущий экземпляр.
Таким образом, в результате работы такого кода
длины массивов у элементов списка будут равны: 8000, 4092, 2048, 1024, 512, 256, 128, 64, 32, 16, 16.
При таких длинах массивов операция обращения к определенному символу в исходной строке выполняется достаточно быстро практически за O(1), так как элементов списка не так много.
Метод Insert()
Метод Insert() работает следующим образом: если в текущем элементе списка(StringBuilder-е) хватает места для вставки, то имеющиеся символы сдвигаются, чтобы дать место новому тексту. Иначе же создается новый элемент списка (StringBuilder), в который копируется часть символов из предыдущего элемента, которые не поместились. Последующие символы не смещаются влево.
Что будет в результате такого кода?
Результат будет отличаться от кода использующего Append(), причем весьма серьезно!
Мы получим очень большой список StringBuilder-ов каждый элемент, которого будет иметь длину 16 символов. В результате чего операция обращения к определенному символу по индексу будет выполняться медленней, чем ожидалось, а именно пропорционально длине списка, то есть O(n).
Метод Remove()
Реализация данного метода существенно усложнилась. Однако надо учесть, что предыдущая реализация копировала большое количество символов, смещая их влево. Здесь же необходимо производить смещение только в пределах одного элемента (StringBuilder-а) в списке.
Метод ToString()
Данный метод проходит по всему связному списку StringBuilder-ов и последовательно копирует символы каждого из элементов списка в результирующую строку.
Сравнение производительности
Пожалуй, самая интересная часть — это сравнение производительности между двумя версиями класса.
Тест 1. Сколько требуется памяти для хранения строки заданной длины.
Как видите, при небольшой длине строки новая реализация проигрывает старой. Оно и понятно, ведь для каждого элемента списка(StringBuilder) требуется информация о длине, емкости, смещении от начала строки + для массива символов overhead. Но как только длина строки становится больше 16384, старая реализация начинает проигрывать (из-за удвоения размера строки, она содержит много неиспользуемых символов).
Тест 2. Метод Append()
Пожалуй, это тот самый метод, в котором новая реализация побеждает. Поскольку при нехватке памяти теперь удваивать длину строки и копировать в нее символы не требуется, то данный метод выполняется значительно быстрее, почти в два раза (точнее в 1,8 раз).
Тест 3. Метод Insert()
Будем производить вставку в строку уже заполненную символами, длиною 1000 символов.
1. Вставка в начало строки
2. Вставка в середину строки
3. Вставка в конец строки
Комментарии излишне — новая реализация проигрывает при вставке в любое место.
Тест 4. Метод Remove()
Будем производить удаление 10 символов из строки уже заполненной символами до тех пор, пока не исчерпаем ее.
Новая реализация выигрывает при удалении почти из любого места, так как теперь не требуется смещать символы оставшейся строки влево (точнее требуется, но не так часто и много как раньше).
Тест 5. Метод ToString()
Как было сказано выше, данный метод проигрывает предыдущей реализации. Предыдущая реализация возвращала просто ссылку на строку, которой она оперировала (при первом вызове), а новая вынуждена собирать результирующую строку по кускам, обходя каждый элемент связного списка.
Новая реализация работает заметно медленнее, если строка формировалась с помощью метода Insert(), поскольку список будет состоять из множества элементов(StringBuilder-ов) длиною в 16 символов.
Тест 6. Обращение по определенному индексу
Учитывая, что теперь StringBuilder представляет собой связный список, операция обращения к строке по определенному индексу становится дорогостоящей. Особенно если он формировался с помощью метода Insert.
Тест 7. Обычный сценарий: множество вызовов Append(), а затем вызов ToString()
Как правило, мы работаем с данным классом по определенному сценарию: множественный вызов метода Append(), за которым следует один вызов ToString(). Реализации данного класса поменялась именно в расчете на данный сценарий.
Вывод
Учитывая новую реализацию класса, при которой лишь множественный вызов метода Append() приводит к увеличению производительности, класс теперь можно было бы назвать StringAppender *)


