Rozumiem zalety StringBuilder.
Ale jeśli chcę połączyć 2 ciągi, to zakładam, że lepiej (szybciej) to zrobić bez StringBuildera. Czy to jest poprawne?
W którym momencie (liczba ciągów) lepiej jest używać StringBuilder?
Rozumiem zalety StringBuilder.
Ale jeśli chcę połączyć 2 ciągi, to zakładam, że lepiej (szybciej) to zrobić bez StringBuildera. Czy to jest poprawne?
W którym momencie (liczba ciągów) lepiej jest używać StringBuilder?
Odpowiedzi:
Gorąco zachęcam do przeczytania Smutnej tragedii teatru mikro-optymalizacji autorstwa Jeffa Atwooda.
Traktuje prostą konkatenację, StringBuilder i inne metody.
Teraz, jeśli chcesz zobaczyć liczby i wykresy, kliknij link;)
Ale jeśli chcę połączyć 2 ciągi, to zakładam, że lepiej (szybciej) zrobić to bez StringBuildera. Czy to jest poprawne?
To prawda, możesz znaleźć, dlaczego dokładnie wyjaśniono bardzo dobrze:
http://www.yoda.arachsys.com/csharp/stringbuilder.html
Podsumowując: jeśli możesz połączyć struny za jednym razem, np
var result = a + " " + b + " " + c + ..
lepiej jest bez StringBuilder, ponieważ wykonywana jest tylko kopia (długość otrzymanego ciągu jest obliczana wcześniej);
Dla struktury takiej jak
var result = a;
result += " ";
result += b;
result += " ";
result += c;
..
za każdym razem tworzone są nowe obiekty, więc warto rozważyć StringBuilder.
Na koniec artykuł podsumowuje te praktyczne zasady:
Reguły kciuka
Kiedy więc należy używać StringBuilder, a kiedy operatorów konkatenacji ciągów?
Zdecydowanie używaj StringBuilder, gdy konkatenujesz w nietrywialnej pętli - zwłaszcza jeśli nie wiesz na pewno (w czasie kompilacji), ile iteracji wykonasz w pętli. Na przykład, czytanie pliku po znaku na raz, budowanie ciągu znaków podczas używania operatora + = jest potencjalnie samobójstwem.
Zdecydowanie używaj operatora konkatenacji, kiedy możesz (czytelnie) określić wszystko, co ma być połączone w jednej instrukcji. (Jeśli masz tablicę elementów do konkatenacji, rozważ jawne wywołanie String.Concat - lub String.Join, jeśli potrzebujesz separatora).
Nie bój się rozbijać literałów na kilka połączonych bitów - wynik będzie taki sam. Możesz zwiększyć czytelność, na przykład dzieląc długi literał na kilka wierszy bez szkody dla wydajności.
Jeśli potrzebujesz pośrednich wyników konkatenacji dla czegoś innego niż podawanie następnej iteracji konkatenacji, StringBuilder nie pomoże. Na przykład, jeśli utworzysz pełne imię i nazwisko z imienia i nazwiska, a następnie dodasz trzecią informację (może pseudonim) na końcu, skorzystasz z StringBuilder tylko wtedy, gdy tego nie zrobisz potrzebujesz ciągu (imię + nazwisko) do innych celów (tak jak w przykładzie, który tworzy obiekt Person).
Jeśli masz do zrobienia tylko kilka konkatenacji i naprawdę chcesz je wykonać w osobnych instrukcjach, nie ma znaczenia, którą drogą pójdziesz. To, który sposób jest bardziej efektywny, będzie zależeć od liczby konkatenacji, rozmiarów łańcucha i kolejności, w jakiej są one konkatenowane. Jeśli naprawdę uważasz, że ten fragment kodu jest wąskim gardłem wydajności, profiluj go lub testuj w obie strony.
System.String jest niezmiennym obiektem - oznacza to, że za każdym razem, gdy zmodyfikujesz jego zawartość, przydzieli nowy ciąg, a to wymaga czasu (i pamięci?). Używając StringBuilder, modyfikujesz rzeczywistą zawartość obiektu bez przydzielania nowej.
Dlatego używaj StringBuilder, gdy musisz wykonać wiele modyfikacji ciągu.
Niezupełnie ... powinieneś użyć StringBuilder, jeśli łączysz duże ciągi lub masz wiele konkatenacji, jak w pętli.
StringBuilder
tylko wtedy, gdy pętla lub konkatenacja stanowi problem ze specyfikacją.
string s = "abcd"
, przynajmniej to ostatnia rzecz Słyszałem ... chociaż ze zmiennymi byłby to najprawdopodobniej Concat.
a + "hello" + "somethingelse"
i nigdy nie musiałem się tym martwić. Jeśli stanie się to problemem, użyję StringBuilder. Ale przede wszystkim nie martwiłem się tym i spędzałem mniej czasu na pisaniu.
Oto prosta aplikacja testowa, aby to udowodnić:
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();
}
}
Wyniki:
30 000 iteracji
1000 iteracji
500 iteracji
Parafrazować
Wtedy policzysz do trzech, nie więcej, nie mniej. Trzy to liczba, którą policzysz, a liczba do obliczenia to trzy. Nie policzysz czterech ani dwóch, chyba że przejdziesz do trzech. Gdy zostanie osiągnięta liczba trzy, która jest trzecią liczbą, lobbuj swój Święty Granat Ręczny z Antiochii
Generalnie używam konstruktora ciągów dla dowolnego bloku kodu, który spowodowałby konkatenację trzech lub więcej ciągów.
Nie ma ostatecznej odpowiedzi, tylko praktyczne zasady. Moje własne zasady wyglądają mniej więcej tak:
StringBuilder
.StringBuilder
.StringBuilder
.Ale jeśli chcę połączyć 2 ciągi, zakładam, że lepiej i szybciej jest to zrobić bez StringBuilder. Czy to jest poprawne?
Tak. Ale co ważniejsze, użycie wanilii w takich sytuacjach jest znacznie bardziej czytelneString
. Z drugiej strony używanie go w pętli ma sens i może być tak czytelne, jak konkatenacja.
Obchodziłbym się z praktycznymi regułami, które jako próg cytują określoną liczbę konkatenacji. Używanie go w pętlach (i tylko w pętlach) jest prawdopodobnie równie przydatne, łatwiejsze do zapamiętania i ma więcej sensu.
Ponieważ trudno jest znaleźć wytłumaczenie tego, które nie ma wpływu ani na opinie, ani na walkę o dumę, pomyślałem, że napiszę trochę kodu na LINQpad, aby samemu to przetestować.
Zauważyłem, że używanie łańcuchów o małych rozmiarach zamiast i.ToString () zmienia czasy odpowiedzi (widoczne w małych pętlach).
Test wykorzystuje różne sekwencje iteracji, aby utrzymać pomiary czasu w rozsądnie porównywalnych zakresach.
Skopiuję kod na końcu, abyś mógł spróbować samemu (wyniki.Charts ... Dump () nie będzie działać poza LINQPad).
Dane wyjściowe (oś X: liczba przetestowanych iteracji, oś Y: czas mierzony w taktach):
Sekwencja iteracji: 2, 3, 4, 5, 6, 7, 8, 9, 10
Sekwencja iteracji: 10, 20, 30, 40, 50, 60, 70, 80
Sekwencja iteracji: 100, 200, 300, 400, 500
Kod (napisany przy użyciu 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];
}
}
Dopóki możesz fizycznie wpisać liczbę konkatenacji (a + b + c ...), nie powinno to robić dużej różnicy. N do kwadratu (przy N = 10) to 100-krotne spowolnienie, co nie powinno być takie złe.
Duży problem występuje, gdy łączysz setki łańcuchów. Przy N = 100 uzyskujesz 10000-krotne spowolnienie. Co jest dość złe.
Nie sądzę, aby istniała cienka granica między tym, kiedy używać, a kiedy nie. Chyba że ktoś przeprowadził obszerne testy, aby wyjść ze złotymi warunkami.
Dla mnie nie użyję StringBuilder, jeśli tylko połączę 2 ogromne ciągi. Jeśli istnieje pętla z nieokreśloną liczbą, prawdopodobnie tak będzie, nawet jeśli pętla może być niewielka.
Pojedyncza konkatenacja nie jest warta używania StringBuilder. Zwykle stosuję 5 konkatenacji jako praktyczną zasadę.