Я понимаю преимущества StringBuilder.
Но если я хочу объединить две строки, я предполагаю, что лучше (быстрее) сделать это без StringBuilder. Это верно?
В какой момент (количество строк) лучше использовать StringBuilder?
Я понимаю преимущества StringBuilder.
Но если я хочу объединить две строки, я предполагаю, что лучше (быстрее) сделать это без StringBuilder. Это верно?
В какой момент (количество строк) лучше использовать StringBuilder?
Ответы:
Я настоятельно рекомендую вам прочитать «Печальная трагедия театра микрооптимизации» Джеффа Этвуда.
Он рассматривает простую конкатенацию, StringBuilder и другие методы.
Теперь, если вы хотите увидеть какие-то числа и графики, перейдите по ссылке;)
Но если я хочу объединить две строки, я предполагаю, что лучше (быстрее) сделать это без 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).
Если вам нужно сделать всего несколько конкатенаций, и вы действительно хотите сделать их отдельными операторами, на самом деле не имеет значения, каким путем вы пойдете. Какой способ более эффективен, будет зависеть от количества конкатенаций, размеров задействованных строк и порядка их конкатенации. Если вы действительно считаете, что этот фрагмент кода является узким местом производительности, профилируйте или проведите сравнительный анализ в обоих направлениях.
System.String - неизменяемый объект - это означает, что всякий раз, когда вы изменяете его содержимое, он выделяет новую строку, а это требует времени (и памяти?). Используя StringBuilder, вы изменяете фактическое содержимое объекта без выделения нового.
Поэтому используйте StringBuilder, когда вам нужно сделать много изменений в строке.
Не совсем ... вам следует использовать StringBuilder, если вы объединяете большие строки или у вас много конкатенаций, например, в цикле.
StringBuilder
только если цикл или конкатенация являются проблемой производительности для спецификаций.
string s = "abcd"
, по крайней мере, это последнее Я слышал ... правда, с переменными, скорее всего, Concat.
a + "hello" + "somethingelse"
и мне никогда не приходилось об этом беспокоиться. Если это станет проблемой, я воспользуюсь StringBuilder. Но я не беспокоился об этом в первую очередь и тратил меньше времени на его написание.
Вот простое тестовое приложение, чтобы доказать это:
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 итераций
1000 итераций
500 итераций
Перефразировать
Затем посчитай до трех, не больше и не меньше. Ты должен сосчитать три, и число будет три. Четыре не считай, и два не считай, за исключением того, что тогда перейди к трем. Как только число три, являющееся третьим числом, будет достигнуто, ты протолкни свою Священную ручную гранату Антиохии.
Обычно я использую построитель строк для любого блока кода, который приведет к объединению трех или более строк.
Однозначного ответа нет, только практические правила. Мои личные правила выглядят примерно так:
StringBuilder
.StringBuilder
.StringBuilder
.Но если я хочу объединить две строки, я предполагаю, что лучше и быстрее сделать это без StringBuilder. Это верно?
Да. Но, что более важно, в таких ситуациях гораздо удобнее использовать ваниль String
. С другой стороны, использование его в цикле имеет смысл и может быть таким же читаемым, как и конкатенация.
Я бы опасался практических правил, в которых в качестве порогового значения указывается конкретное число конкатенации. Использование его в циклах (и только в циклах), вероятно, так же полезно, легче запоминается и имеет больше смысла.
Так как этому трудно найти объяснение, которое бы не зависело от мнений и не сопровождалось битвой гордости, я решил написать небольшой код на LINQpad, чтобы проверить это сам.
Я обнаружил, что использование строк небольшого размера вместо использования i.ToString () изменяет время отклика (видимое в небольших циклах).
В тесте используются разные последовательности итераций, чтобы измерения времени находились в разумно сопоставимых диапазонах.
Я скопирую код в конце, чтобы вы могли попробовать это сами (results.Charts ... Dump () не будет работать вне LINQPad).
Выходные данные (ось X: количество протестированных итераций, ось Y: время в тиках):
Последовательность итераций: 2, 3, 4, 5, 6, 7, 8, 9, 10
Последовательность итераций: 10, 20, 30, 40, 50, 60, 70, 80
Последовательность итераций: 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];
}
}
Пока вы можете физически набрать количество конкатенаций (a + b + c ...), это не должно иметь большого значения. N в квадрате (при N = 10) - это 100-кратное замедление, что не должно быть так уж плохо.
Большая проблема - когда вы объединяете сотни строк. При N = 100 вы получаете замедление в 10000 раз. Что очень плохо.
Я не думаю, что есть тонкая грань между тем, когда использовать, а когда нет. Если, конечно, кто-то не выполнил какие-то обширные испытания, чтобы выйти с золотыми условиями.
Для меня я не буду использовать StringBuilder, если просто объединю две огромные строки. Если есть цикл с неопределенным счетчиком, я, скорее всего, это сделаю, даже если цикл может быть небольшим.