Odpowiedzi:
Tak, różnica w wydajności jest znacząca. Zobacz artykuł z bazy wiedzy „ Jak poprawić wydajność konkatenacji ciągów w Visual C # ”.
Zawsze starałem się najpierw napisać kod dla zachowania przejrzystości, a następnie zoptymalizować go pod kątem wydajności. To o wiele łatwiejsze niż robienie tego na odwrót! Jednak widząc ogromną różnicę wydajności w moich aplikacjach między tymi dwoma, teraz myślę o tym nieco ostrożniej.
Na szczęście stosunkowo łatwo jest przeprowadzić analizę wydajności kodu, aby zobaczyć, gdzie spędzasz czas, a następnie zmodyfikować go, aby używał w StringBuilder
razie potrzeby.
Aby wyjaśnić, co Gillian powiedziała o 4 łańcuchach, jeśli masz coś takiego:
string a,b,c,d;
a = b + c + d;
wtedy byłoby szybciej przy użyciu ciągów i operatora plusa. Wynika to z faktu, że (podobnie jak Java, jak wskazuje Eric), wewnętrznie używa StringBuilder automatycznie (w rzeczywistości używa prymitywów, których używa StringBuilder)
Jeśli jednak to, co robisz, jest bliższe:
string a,b,c,d;
a = a + b;
a = a + c;
a = a + d;
Następnie musisz jawnie użyć StringBuilder. .Net nie tworzy tutaj automatycznie StringBuilder, ponieważ byłoby to bezcelowe. Na końcu każdej linii „a” musi być (niezmiennym) łańcuchem, więc musiałby utworzyć i umieścić StringBuilder w każdej linii. Aby uzyskać szybkość, musisz używać tego samego StringBuilder, dopóki nie skończysz budować:
string a,b,c,d;
StringBuilder e = new StringBuilder();
e.Append(b);
e.Append(c);
e.Append(d);
a = e.ToString();
a
jest zmienną lokalną, a obiekt, do którego się odwołuje, nie został przypisany do żadnej innej zmiennej (która może być dostępna dla innego wątku), dobry optymalizator może stwierdzić, że żaden inny koda
nie uzyskuje dostępu podczas tej sekwencji linie; tylko końcowa wartość spraw. Mogłoby to potraktować te trzy wiersze kodu tak, jakby były napisane . a
a = b + c + d;
StringBuilder jest preferowany, JEŚLI robisz wiele pętli lub widelców w swoim kodzie przepustowym ... jednak dla wydajności PURE, jeśli możesz uniknąć SINGLE deklaracji ciągu, to jest to o wiele bardziej wydajne.
Na przykład:
string myString = "Some stuff" + var1 + " more stuff"
+ var2 + " other stuff" .... etc... etc...;
jest bardziej wydajny niż
StringBuilder sb = new StringBuilder();
sb.Append("Some Stuff");
sb.Append(var1);
sb.Append(" more stuff");
sb.Append(var2);
sb.Append("other stuff");
// etc.. etc.. etc..
W takim przypadku StringBuild można uznać za łatwiejsze w utrzymaniu, ale nie jest ono bardziej wydajne niż deklaracja pojedynczego ciągu.
9 razy na 10 ... użyj konstruktora ciągów.
Na marginesie: string + var jest również bardziej wydajny niż string.Formatowanie (ogólnie), które korzysta z StringBuilder wewnętrznie (w razie wątpliwości ... sprawdź reflektor!)
Prosty przykład pokazujący różnicę prędkości przy stosowaniu String
konkatenacji vs StringBuilder
:
System.Diagnostics.Stopwatch time = new Stopwatch();
string test = string.Empty;
time.Start();
for (int i = 0; i < 100000; i++)
{
test += i;
}
time.Stop();
System.Console.WriteLine("Using String concatenation: " + time.ElapsedMilliseconds + " milliseconds");
Wynik:
Za pomocą konkatenacji ciągów: 15423 milisekundy
StringBuilder test1 = new StringBuilder();
time.Reset();
time.Start();
for (int i = 0; i < 100000; i++)
{
test1.Append(i);
}
time.Stop();
System.Console.WriteLine("Using StringBuilder: " + time.ElapsedMilliseconds + " milliseconds");
Wynik:
Za pomocą StringBuilder: 10 milisekund
W rezultacie pierwsza iteracja zajęła 15423 ms, a druga iteracja StringBuilder
zajęła 10 ms.
Wydaje mi się, że używanie StringBuilder
jest szybsze, dużo szybsze.
Ten test porównawczy pokazuje, że regularne łączenie jest szybsze przy łączeniu 3 lub mniejszej liczby łańcuchów.
http://www.chinhdo.com/20070224/stringbuilder-is-not-always-faster/
StringBuilder może znacznie poprawić wykorzystanie pamięci, szczególnie w przypadku dodania 500 łańcuchów razem.
Rozważ następujący przykład:
string buffer = "The numbers are: ";
for( int i = 0; i < 5; i++)
{
buffer += i.ToString();
}
return buffer;
Co dzieje się w pamięci? Tworzone są następujące ciągi znaków:
1 - "The numbers are: "
2 - "0"
3 - "The numbers are: 0"
4 - "1"
5 - "The numbers are: 01"
6 - "2"
7 - "The numbers are: 012"
8 - "3"
9 - "The numbers are: 0123"
10 - "4"
11 - "The numbers are: 01234"
12 - "5"
13 - "The numbers are: 012345"
Dodając te pięć liczb na końcu łańcucha, stworzyliśmy 13 obiektów łańcuchowych! I 12 z nich było bezużytecznych! Łał!
StringBuilder rozwiązuje ten problem. Nie jest to „zmienny ciąg”, jak często słyszymy ( wszystkie ciągi w .NET są niezmienne ). Działa poprzez utrzymanie wewnętrznego bufora, tablicy znaków. Wywołanie Append () lub AppendLine () dodaje ciąg do pustego miejsca na końcu tablicy char; jeśli tablica jest zbyt mała, tworzy nową, większą tablicę i kopiuje tam bufor. Tak więc w powyższym przykładzie StringBuilder może potrzebować tylko jednej tablicy do przechowywania wszystkich 5 dodatków do łańcucha - w zależności od wielkości bufora. Możesz powiedzieć StringBuilder, jak duży powinien być jego bufor w konstruktorze.
i.ToString()
. Zatem w StringBuilder nadal musisz tworzyć ciągi 6 + 1; zredukował tworzenie 13 łańcuchów do utworzenia 7 łańcuchów. Ale to wciąż niewłaściwy sposób patrzenia na to; utworzenie ciągów sześciu liczb nie jest istotne. Konkluzja: po prostu nie powinieneś wspominać o sześciu ciągach utworzonych przez i.ToString()
; nie są one częścią porównania wydajności.
Tak, StringBuilder
daje lepszą wydajność podczas powtarzania operacji na łańcuchu. Dzieje się tak, ponieważ wszystkie zmiany są wprowadzane w jednym wystąpieniu, dzięki czemu można zaoszczędzić dużo czasu zamiast tworzyć nowe, takie jak wystąpienie String
.
String
System
przestrzeni nazwStringBuilder
(zmienny ciąg)
System.Text
przestrzeni nazwZdecydowanie polecam artykuł dotnet mob: String Vs StringBuilder w C # .
Powiązane pytanie przepełnienia stosu: Zmienność ciągu, gdy ciąg nie zmienia się w C #? .
String Vs String Builder:
Po pierwsze musisz wiedzieć, w którym zgromadzeniu żyją te dwie klasy?
Więc,
ciąg jest obecny w System
przestrzeni nazw.
i
StringBuilder jest obecny w System.Text
przestrzeni nazw.
Do deklaracji ciągu :
Musisz dołączyć System
przestrzeń nazw. coś takiego.
Using System;
i
Dla StringBuilder deklaracji :
Musisz dołączyć System.text
przestrzeń nazw. coś takiego.
Using System.text;
Teraz przyjdź aktualne pytanie.
Jaki jest differene pomiędzy ciągiem & StringBuilder ?
Główną różnicą między tymi dwoma jest to, że:
strunowy jest niezmienny.
i
StringBuilder można modyfikować.
Teraz omówmy różnicę między niezmienną a zmienną
Zmienny: oznacza zmienny .
Niezmienny: oznacza, że nie można go zmienić.
Na przykład:
using System;
namespace StringVsStrigBuilder
{
class Program
{
static void Main(string[] args)
{
// String Example
string name = "Rehan";
name = name + "Shah";
name = name + "RS";
name = name + "---";
name = name + "I love to write programs.";
// Now when I run this program this output will be look like this.
// output : "Rehan Shah RS --- I love to write programs."
}
}
}
W tym przypadku zamierzamy zmienić ten sam obiekt 5 razy.
Więc oczywiste pytanie jest takie! Co tak naprawdę dzieje się pod maską, kiedy zmieniamy ten sam ciąg 5 razy.
Tak się dzieje, gdy zmieniamy ten sam ciąg 5 razy.
spójrzmy na postać.
Wyjaśnienie:
Kiedy po raz pierwszy inicjujemy tę zmienną „name” na „Rehan” tj string name = "Rehan"
Ta zmienna jest tworzona na stosie „name” i wskazuje na tę wartość „Rehan”. po wykonaniu tej linii: „name = name +„ Shah ”. zmienna referencyjna nie wskazuje już na ten obiekt„ Rehan ”, teraz wskazuje na„ Shah ”i tak dalej.
string
Jest zatem niezmienne, że po utworzeniu obiektu w pamięci nie możemy go zmienić.
Tak więc, kiedy konatynizujemy name
zmienną, poprzedni obiekt pozostaje tam w pamięci i tworzony jest kolejny nowy obiekt łańcuchowy ...
Tak więc z powyższej figury mamy pięć obiektów, cztery obiekty są wyrzucane, w ogóle nie są używane. Wciąż pozostają w pamięci i wykorzystują ilość pamięci. „Garbage Collector” jest odpowiedzialny za to, aby oczyścić zasoby z pamięci.
Tak więc w przypadku łańcucha znaków w dowolnym momencie, gdy manipulujemy nim w kółko, mamy kilka obiektów utworzonych i pozostają tam w pamięci.
Oto historia zmiennego łańcucha.
Teraz spójrzmy na obiekt StringBuilder. Na przykład:
using System;
using System.Text;
namespace StringVsStrigBuilder
{
class Program
{
static void Main(string[] args)
{
// StringBuilder Example
StringBuilder name = new StringBuilder();
name.Append("Rehan");
name.Append("Shah");
name.Append("RS");
name.Append("---");
name.Append("I love to write programs.");
// Now when I run this program this output will be look like this.
// output : "Rehan Shah Rs --- I love to write programs."
}
}
}
W tym przypadku zamierzamy zmienić ten sam obiekt 5 razy.
Więc oczywiste pytanie jest takie! Co tak naprawdę dzieje się pod maską, kiedy zmieniamy ten sam StringBuilder 5 razy.
Tak się dzieje, gdy zmieniamy ten sam StringBuilder 5 razy.
Wyjaśnienie: W przypadku obiektu StringBuilder. nie dostaniesz nowego obiektu. Ten sam obiekt zostanie zmieniony w pamięci, więc nawet jeśli zmienisz obiekt i powiedzmy 10 000 razy, nadal będziemy mieć tylko jeden obiekt stringBuilder.
Nie masz dużo obiektów śmieciowych ani niereferencjonowanych obiektów stringBuilder, ponieważ można to zmienić. Jest zmienny, co oznacza, że zmienia się z czasem?
Różnice:
StringBuilder zmniejsza liczbę przydziałów i przypisań kosztem dodatkowej pamięci używanej. Właściwie użyte, może całkowicie wyeliminować potrzebę kompilacji do przydzielania coraz większych ciągów znaków, aż do znalezienia wyniku.
string result = "";
for(int i = 0; i != N; ++i)
{
result = result + i.ToString(); // allocates a new string, then assigns it to result, which gets repeated N times
}
vs.
String result;
StringBuilder sb = new StringBuilder(10000); // create a buffer of 10k
for(int i = 0; i != N; ++i)
{
sb.Append(i.ToString()); // fill the buffer, resizing if it overflows the buffer
}
result = sb.ToString(); // assigns once
Wydajność operacji konkatenacji dla obiektu String lub StringBuilder zależy od częstotliwości alokacji pamięci. Operacja konkatenacji String zawsze przydziela pamięć, natomiast operacja konkatenacji StringBuilder przydziela pamięć tylko wtedy, gdy bufor obiektu StringBuilder jest zbyt mały, aby pomieścić nowe dane. W związku z tym klasa String jest preferowana w przypadku operacji konkatenacji, jeśli połączona jest stała liczba obiektów String. W takim przypadku poszczególne operacje konkatenacji mogą nawet zostać połączone przez kompilatora w jedną operację. Obiekt StringBuilder jest preferowany dla operacji konkatenacji, jeśli dowolna liczba łańcuchów jest konkatenowana; na przykład, jeśli pętla łączy losową liczbę ciągów danych wejściowych użytkownika.
Źródło: MSDN
StringBuilder
jest lepszy do budowania łańcucha z wielu niestałych wartości.
Jeśli budujesz ciąg z wielu stałych wartości, takich jak wiele wierszy wartości w dokumencie HTML lub XML lub innych fragmentach tekstu, możesz uniknąć dodawania do tego samego ciągu, ponieważ prawie wszystkie kompilatory „stałe składanie”, proces zmniejszania parsowania drzewa, gdy masz kilka ciągłych manipulacji (jest również używany, gdy piszesz coś takiego int minutesPerYear = 24 * 365 * 60
). A w prostych przypadkach z dołączonymi do siebie niestałymi wartościami kompilator .NET zredukuje kod do czegoś podobnego do tego, co StringBuilder
robi.
Ale gdy kompilator nie może zredukować Twojego dodatku do czegoś prostszego, będziesz chciał StringBuilder
. Jak wskazuje fizch, jest to bardziej prawdopodobne w pętli.
W .NET StringBuilder jest nadal szybszy niż dodawanie ciągów. Jestem prawie pewien, że w Javie po prostu dodają StringBuffer pod maską, gdy dodasz ciągi, więc tak naprawdę nie ma różnicy. Nie jestem pewien, dlaczego jeszcze tego nie zrobili w .NET.
Rozważ „ Smutną tragedię teatru mikrooptymalizacji ”.
Używanie ciągów do konkatenacji może prowadzić do złożoności środowiska wykonawczego w kolejności od O(n^2)
.
Jeśli używasz a StringBuilder
, musisz wykonać o wiele mniej kopiowania pamięci. Dzięki StringBuilder(int capacity)
możesz zwiększyć wydajność, jeśli potrafisz oszacować, jak duży będzie finał String
. Nawet jeśli nie jesteś precyzyjny, prawdopodobnie będziesz musiał zwiększyć pojemność StringBuilder
tylko kilka razy, co może również pomóc w wydajności.
Widziałem znaczny wzrost wydajności dzięki użyciu EnsureCapacity(int capacity)
wywołania metody w instancji StringBuilder
przed użyciem go do przechowywania ciągów znaków. Zwykle nazywam to w wierszu kodu po utworzeniu wystąpienia. Ma taki sam efekt, jakbyś utworzył taką instancję StringBuilder
:
var sb = new StringBuilder(int capacity);
To połączenie alokuje potrzebną pamięć z wyprzedzeniem, co powoduje mniej alokacji pamięci podczas wielu Append()
operacji. Musisz zgadnąć, ile pamięci potrzebujesz, ale w przypadku większości aplikacji nie powinno to być zbyt trudne. Zwykle mylę się po stronie trochę za dużej pamięci (mówimy około 1k).
EnsureCapacity
bezpośrednio po StringBuilder
utworzeniu instancji. Po prostu instancję StringBuilder
tak: var sb = new StringBuilder(int capacity)
.
W nawiązaniu do poprzednich odpowiedzi, pierwszą rzeczą, którą zawsze robię, gdy myślę o takich sprawach, jest stworzenie małej aplikacji testowej. Wewnątrz tej aplikacji wykonaj test czasowy dla obu scenariuszy i przekonaj się, co jest szybsze.
IMHO, dołączając ponad 500 wpisów w łańcuchach, zdecydowanie należy użyć StringBuilder.
String i StringBuilder są w rzeczywistości niezmienne, StringBuilder ma wbudowane bufory, które pozwalają na bardziej efektywne zarządzanie jego rozmiarem. Kiedy StringBuilder musi zmienić rozmiar, następuje ponowne przydzielenie go na stercie. Domyślnie ma wielkość 16 znaków, możesz ustawić to w konstruktorze.
na przykład.
StringBuilder sb = new StringBuilder (50);
Łączenie łańcuchów będzie cię kosztować więcej. W Javie możesz użyć StringBuffer lub StringBuilder w zależności od potrzeb. Jeśli chcesz zsynchronizowaną i bezpieczną dla wątków implementację, wybierz StringBuffer. Będzie to szybsze niż konkatenacja ciągów.
Jeśli nie potrzebujesz implementacji synchronicznej lub bezpiecznej dla wątków, wybierz StringBuilder. Będzie to szybsze niż konkatenacja String, a także szybsze niż StringBuffer, ponieważ nie jest to narzut związany z synchronizacją.
StringBuilder jest prawdopodobnie preferowany. Powodem jest to, że przydziela więcej miejsca niż obecnie potrzebne (ustawiasz liczbę znaków), aby zostawić miejsce na przyszłe dodatki. Następnie te przyszłe załączniki, które mieszczą się w bieżącym buforze, nie wymagają alokacji pamięci ani odśmiecania, co może być kosztowne. Zasadniczo używam StringBuilder do łączenia złożonych łańcuchów lub formatowania wielokrotnego, a następnie przekształcam się w normalny ciąg znaków, gdy dane są kompletne, i chcę ponownie obiektu niezmiennego.
Zasadą ogólną jest to, że jeśli muszę ustawić wartość ciągu więcej niż jeden raz lub jeśli ciąg jest dołączany, to musi to być kreator ciągu. Widziałem aplikacje, które napisałem w przeszłości, zanim dowiedziałem się o konstruktorach łańcuchów, które miały ogromny ślad pamięci, który po prostu wydaje się stale rosnąć. Zmiana tych programów na użycie konstruktora ciągów znacznie zmniejsza zużycie pamięci. Teraz przysięgam na konstruktora strun.
Moje podejście zawsze polegało na użyciu StringBuilder podczas łączenia 4 lub więcej ciągów LUB Gdy nie wiem, jak mogą się odbywać połączenia.
StringBuilder
jest znacznie bardziej wydajny, ale nie zobaczysz tej wydajności, chyba że wykonasz dużą ilość modyfikacji łańcucha.
Poniżej znajduje się krótki fragment kodu, który stanowi przykład wydajności. Jak widać, naprawdę zaczyna się zauważać znaczny wzrost wydajności, gdy przejdziesz do dużych iteracji.
Jak widać, 200 000 iteracji zajęło 22 sekundy, podczas gdy 1 milion iteracji z użyciem tego StringBuilder
było prawie natychmiastowe.
string s = string.Empty;
StringBuilder sb = new StringBuilder();
Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());
for (int i = 0; i <= 50000; i++)
{
s = s + 'A';
}
Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();
Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());
for (int i = 0; i <= 200000; i++)
{
s = s + 'A';
}
Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();
Console.WriteLine("Beginning Sb append at " + DateTime.Now.ToString());
for (int i = 0; i <= 1000000; i++)
{
sb.Append("A");
}
Console.WriteLine("Finished Sb append at " + DateTime.Now.ToString());
Console.ReadLine();
Wynik powyższego kodu:
Początkowy ciąg + o 28.01.2013 16:55:40.
Ukończony ciąg + na 28.01.2013 16:55:40.
Początkowy ciąg + o 28.01.2013 16:55:40.
Ukończony ciąg + na 28.01.2013 16:56:02.
Początek Sb dołączamy o 28.01.2013 16:56:02.
Gotowe Sb dołączają o 28.01.2013 16:56:02.
StringBuilder będzie działał lepiej, z punktu widzenia pamięci. Jeśli chodzi o przetwarzanie, różnica w czasie wykonania może być nieznaczna.