Я розумію переваги StringBuilder.
Але якщо я хочу об'єднати 2 рядки, то я припускаю, що краще (швидше) це зробити без StringBuilder. Це правильно?
У який момент (кількість рядків) стає краще використовувати StringBuilder?
Я розумію переваги StringBuilder.
Але якщо я хочу об'єднати 2 рядки, то я припускаю, що краще (швидше) це зробити без StringBuilder. Це правильно?
У який момент (кількість рядків) стає краще використовувати StringBuilder?
Відповіді:
Тепло пропоную вам прочитати «Сумну трагедію театру мікрооптимізації» Джефа Етвуда.
Він розглядає просту конкатенацію проти StringBuilder проти інших методів.
Тепер, якщо ви хочете побачити деякі цифри та графіки, перейдіть за посиланням;)
Але якщо я хочу поєднати 2 рядки, то я припускаю, що краще (швидше) це зробити без 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
.Але якщо я хочу об'єднати 2 рядки, то я припускаю, що це краще і швидше робити без 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, якщо просто об'єднаю 2 величезні рядки. Якщо є цикл з недетермінованим рахунком, я, швидше за все, маю, навіть якщо цикл може бути невеликим.