StringBuilder i przetwarzanie tekstu
Raport, który powstaje w pętli, i dane wczytane jako jeden wiersz z separatorem — dwa zadania, przy których zwykłe sklejanie napisów przestaje wystarczać.
Czego się dziś nauczysz
- Wyjaśnisz, dlaczego sklejanie napisów w długiej pętli jest kosztowne
- Zbudujesz tekst przy użyciu
StringBuilder - Podzielisz wiersz danych metodą
Splitz odpowiednimi opcjami - Odrzucisz uszkodzone dane, zanim sięgniesz po indeks tablicy
- Złożysz wyrównany raport z nagłówkiem, sumą i podsumowaniem błędów
Dlaczego sklejanie napisów bywa kosztowne
Z lekcji 08 wiesz, że napis jest niemutowalny: każda operacja tworzy nowy napis, a stary zostaje bez zmian. Przy kilku sklejeniach to nie ma znaczenia. Przy tysiącach — ma.
string raport = "";
for (int i = 1; i <= 50000; i++)
{
raport += i + ";"; // za kazdym razem powstaje NOWY napis
}
W obiegu numer 40 000 program kopiuje napis o długości około 200 tysięcy znaków tylko po to, żeby dopisać kilka znaków na końcu. Łącznie kopiuje miliardy znaków.
StringBuilder to bufor, który można rozbudowywać w miejscu. Dopiero na końcu zamieniamy go na napis.
using System.Text;
StringBuilder budowniczy = new StringBuilder();
for (int i = 1; i <= 50000; i++)
{
budowniczy.Append(i).Append(';');
}
string raport = budowniczy.ToString();
| Sytuacja | Czego użyć |
|---|---|
| Sklejenie 2–5 kawałków | interpolacja $"..." albo zwykłe + |
| Łączenie elementów tablicy albo listy | string.Join |
| Budowanie tekstu w pętli o wielu obiegach | StringBuilder |
Sklejanie stałych, np. "a" + "b" | obojętne — kompilator zrobi to raz, przy budowaniu |
Nie przesadzaj
StringBuilder w pętli o pięciu obiegach nic nie przyspieszy, a kod będzie dłuższy. Sięgaj po niego, gdy liczba dopisań jest duża albo nieznana z góry.
Co potrafi StringBuilder
using System.Text;
StringBuilder sb = new StringBuilder();
sb.Append("Uczen"); // dopisuje
sb.Append(':').Append(' '); // wywolania mozna laczyc lancuchem
sb.Append(42); // liczby tez - bez ToString
sb.AppendLine(); // dopisuje znak konca wiersza
sb.AppendLine("druga linia");
sb.Insert(0, ">> "); // wstawia na podanej pozycji
sb.Replace("Uczen", "Zawodnik"); // zamienia WSZYSTKIE wystapienia
Console.WriteLine(sb.Length); // dlugosc buforu
Console.WriteLine(sb.ToString());
>> Zawodnik: 42
druga linia
Bufor zachowuje się jak tablica znaków, którą można też czytać i modyfikować po indeksie: sb[0]. Metoda Clear() opróżnia go bez tworzenia nowego obiektu — przydaje się, gdy budujesz wiele raportów po kolei.
ToString wywołaj raz
sb.ToString() tworzy nowy napis, kopiując cały bufor. Wywołanie go w pętli niweczy cały zysk. Buduj do końca, a konwertuj na sam koniec.
Dzielenie i składanie tekstu
Dane z pliku albo od użytkownika najczęściej przychodzą jako jeden wiersz z separatorem. Rozbijamy go metodą Split, a składamy metodą string.Join.
string wiersz = " Ala ;; Ola ; Jan ";
string[] czesci = wiersz.Split(';', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
Console.WriteLine(czesci.Length); // 3
Console.WriteLine(string.Join(" | ", czesci)); // Ala | Ola | Jan
Opcja StringSplitOptions | Działanie |
|---|---|
None | Zachowanie domyślne — puste fragmenty i spacje zostają. |
RemoveEmptyEntries | Pomija puste fragmenty, np. z podwójnego separatora. |
TrimEntries | Obcina spacje z obu stron każdego fragmentu. |
Kiedy wiersz ma stałą budowę — imie;punkty — po podziale sprawdzamy liczbę części przed sięgnięciem po indeks:
string[] pola = wiersz.Split(';');
if (pola.Length != 2)
{
Console.WriteLine("Wiersz ma zla liczbe pol - pomijam.");
}
else if (!int.TryParse(pola[1], out int punkty))
{
Console.WriteLine("Punkty nie sa liczba - pomijam.");
}
else
{
Console.WriteLine($"{pola[0].Trim()} ma {punkty} pkt");
}
Split nie sprawdza za ciebie
pola[1] na wierszu bez średnika kończy się wyjątkiem IndexOutOfRangeException. Zawsze najpierw Length, potem indeks — ta sama zasada, co przy tablicach.
Przykład: raport z surowych danych
Program dostaje wiersze w formacie imie;punkty, odrzuca uszkodzone i buduje wyrównaną tabelkę.
using System.Text;
class Program
{
static void Main()
{
string[] surowe =
{
"Ala;18",
" Ola ; 24 ",
"uszkodzony wiersz",
"Jan;abc",
"Ewa;7"
};
StringBuilder raport = new StringBuilder();
raport.AppendLine("IMIE PUNKTY");
raport.AppendLine(new string('-', 26));
int poprawne = 0, bledne = 0, suma = 0;
foreach (string wiersz in surowe)
{
string[] pola = wiersz.Split(';', StringSplitOptions.TrimEntries);
if (pola.Length != 2 || !int.TryParse(pola[1], out int punkty))
{
bledne++;
continue;
}
poprawne++;
suma += punkty;
raport.AppendLine($"{pola[0].PadRight(20)}{punkty,6}");
}
raport.AppendLine(new string('-', 26));
raport.AppendLine($"{"RAZEM".PadRight(20)}{suma,6}");
raport.AppendLine($"Poprawnych: {poprawne}, blednych: {bledne}");
Console.Write(raport.ToString());
}
}
IMIE PUNKTY
--------------------------
Ala 18
Ola 24
Ewa 7
--------------------------
RAZEM 49
Poprawnych: 3, blednych: 2
Co dzieje się po kolei
Splitz opcjąTrimEntriesod razu usuwa spacje, więc" Ola "nie wymaga osobnegoTrim.- Wiersz bez średnika daje jednoelementową tablicę — warunek
pola.Length != 2wyłapuje go przed dostępem dopola[1]. continuepomija resztę obiegu; licznik błędnych rośnie, a program się nie przerywa.new string('-', 26)tworzy linię z 26 myślników bez pętli.- Raport powstaje w buforze i trafia na ekran jednym
Console.Write— łatwo go potem zapisać do pliku zamiast wypisywać.
Zestawienie metod
| Metoda | Zwraca | Działanie i ograniczenia |
|---|---|---|
sb.Append(x) | StringBuilder | Dopisuje na końcu bufora. Zwraca ten sam obiekt, więc wywołania można łączyć. Przyjmuje też liczby i znaki. |
sb.AppendLine(x) | StringBuilder | Jak Append, ale dokłada znak końca wiersza właściwy dla systemu. |
sb.Insert(i, x) | StringBuilder | Wstawia na pozycji i. Indeks większy niż Length daje wyjątek. |
sb.Replace(a, b) | StringBuilder | Zamienia wszystkie wystąpienia w buforze. |
sb.Clear() | StringBuilder | Opróżnia bufor, zachowując obiekt. |
sb.ToString() | string | Tworzy napis z zawartości bufora. Wywołuj raz, na końcu. |
s.Split(znak, opcje) | string[] | Dzieli napis. Bez opcji zostawia puste fragmenty. Napisu źródłowego nie zmienia. |
string.Join(sep, kolekcja) | string | Skleja elementy separatorem. Działa dla tablic i list dowolnego typu. |
s.PadRight(n) / PadLeft(n) | string | Dopełnia spacjami do długości n. Napis dłuższy niż n nie jest przycinany. |
new string(znak, n) | string | Napis z n powtórzeń znaku — wygodne przy liniach i słupkach. |
s.Contains(x) | bool | Czy napis zawiera podany fragment. Wersja z StringComparison pozwala pominąć wielkość liter. |
Na co uważać
| Zapis | Problem |
|---|---|
sb.ToString() w każdym obiegu | Kopiuje cały bufor za każdym razem — zysk z StringBuilder znika. |
sb.Append("a") + "b" | Append zwraca bufor, nie napis. Sklejanie z napisem daje bezsensowny wynik — dopisuj kolejnym Append. |
pola[1] bez sprawdzenia Length | IndexOutOfRangeException na pierwszym uszkodzonym wierszu. |
Split(';') na danych z separatorem w treści | Imię „Kowalski; Jan” rozpadnie się na dwa pola. Przy zapisie odrzucaj albo zamieniaj znak separatora w danych. |
Replace na string bez przypisania | Napis jest niemutowalny — s.Replace(...) bez s = nie zmienia niczego. W StringBuilder zmienia bufor w miejscu. |
Co jeszcze da się zrobić z tekstem
Skoro umiesz już rozbierać i składać tekst, warto zobaczyć klasyczne algorytmy tekstowe. Lekcja 30 pokazuje szyfr Cezara, szyfr podstawieniowy, XOR i sumy kontrolne — wszystkie na tym, co już znasz.
Zadania
Zbuduj napis z liczbami od 1 do 20 oddzielonymi przecinkiem — raz przez +=, raz przez StringBuilder, raz przez string.Join. Porównaj długość kodu i czytelność.
Napisz fragment, który otacza podany tekst ramką z gwiazdek dopasowaną do jego długości. Użyj new string('*', n) i PadRight.
Z napisu "Ala;;Ola; ;Jan;" zrób tablicę trzech imion bez pustych wpisów i bez spacji, a potem złóż ją z powrotem separatorem ", ".
Wczytaj zdanie i wypisz je ze słowami w odwrotnej kolejności, zachowując pojedyncze spacje. Podpowiedź: Split, pętla od końca i StringBuilder.
Dla wczytanego zdania podaj: liczbę znaków, liczbę słów, najdłuższe słowo i liczbę samogłosek. Wynik zbuduj w StringBuilder i wypisz jednym wywołaniem.
Mając tablicę wierszy w formacie imie;klasa;punkty, zbuduj raport z nagłówkiem, wyrównanymi kolumnami, sumą i średnią punktów oraz listą wierszy odrzuconych wraz z numerem i powodem odrzucenia.
Co trzeba zapamiętać
- Napis jest niemutowalny — każde
+=tworzy nowy obiekt. StringBuilderbuduje tekst w buforze;ToStringwywołujemy raz, na końcu.Appendzwraca ten sam bufor, więc wywołania można łączyć w łańcuch.Splitz opcjamiRemoveEmptyEntriesiTrimEntriesoszczędza ręcznego czyszczenia.- Po podziale zawsze sprawdź liczbę pól, zanim sięgniesz po indeks.
- Do sklejenia kilku kawałków wystarczy interpolacja;
StringBuilderjest do pętli o wielu obiegach.
Dokumentacja: Microsoft Learn — temat tej lekcji.