CodeGym /Kursy /C# SELF /Zapis plików tekstowych: S...

Zapis plików tekstowych: StreamWriter

C# SELF
Poziom 36 , Lekcja 3
Dostępny

1. Wprowadzenie

StreamWriter — to jeden z kluczowych klas przestrzeni nazw System.IO w .NET, przeznaczony do wygodnego zapisywania danych tekstowych do plików przez strumień.

Dlaczego nie można od razu używać FileStream?
FileStream operuje wyłącznie na bajtach. Jeśli spróbujesz zapisać string przez niego, będziesz musiał samodzielnie konwertować tekst na bajty i martwić się o kodowanie (potem na pewno będziesz chciał wrócić do StreamWriter).
StreamWriter sam ogarnia te wszystkie rzeczy: dajesz mu stringa — on zapisuje odpowiednie bajty do pliku.

Główne plusy:

  • Łatwo pisać stringi, nie myśląc o konwersji na bajty.
  • Są metody do zapisu tekstu linia po linii.
  • Możesz kontrolować buforowanie i kodowanie (i jeszcze się tego nauczymy).

Najprostszy przykład

using System.IO;

string path = "output.txt";

using (StreamWriter writer = new StreamWriter(path))
{
    writer.WriteLine("Cześć, świecie!");
    writer.WriteLine("To jest drugi wiersz.");
}
// Po wyjściu z klamerek using StreamWriter gwarantowanie zwalnia plik.

Co tu się dzieje?

  • Otwierany jest plik do zapisu (jeśli pliku nie ma — zostanie utworzony).
  • Każda linia jest zapisywana jako osobny wiersz w pliku (metoda WriteLine).
  • Po bloku using plik automatycznie się zamyka, nawet jeśli pojawią się błędy.

Jeśli otworzysz plik output.txt po wykonaniu tego programu, zobaczysz dwie linie tekstu, dokładnie tak, jak się spodziewałeś.

Ważny niuans

Jeśli plik już istnieje, zostanie nadpisany od zera! Wszystko, co było w środku — zniknie. Więc uważaj: nie trzymaj w takich plikach ważnej pracy magisterskiej albo jedynego egzemplarza rachunku za prąd.

2. Zapis danych do strumienia

Główne metody StreamWriter

Metoda Opis
Write(string)
Zapisuje string bez przejścia do nowej linii
WriteLine(string)
Zapisuje string z przejściem do nowej linii
Flush()
Wymusza zapis bufora do pliku (ręcznie robi się to rzadko)
Close()
/
Dispose()
Zamyka strumień i zwalnia zasób (robi to using)
BaseStream
Dostęp do bazowego strumienia (np. FileStream)

Metoda Write()

Zapisuje dane bez przechodzenia do nowej linii. Wszystko, co napiszesz dalej, będzie w tej samej linii pliku.

Metoda WriteLine()

Zapisuje dane z automatycznym dodaniem znaku końca linii (\r\n w Windows, \n w systemach Unix).
To jakbyś wciskał Enter po każdym zapisie.

Write() vs WriteLine(): demonstracja różnicy

using (var writer = new StreamWriter("example.txt"))
{
    writer.Write("Pierwszy ");
    writer.Write("akapit. ");
    writer.WriteLine("Dopisaliśmy linię, Enter!");
    writer.Write("Drugi akapit.");
}

Teraz example.txt będzie wyglądał mniej więcej tak:

Pierwszy akapit. Dopisaliśmy linię, Enter!
Drugi akapit.

3. Praca z kodowaniem

Domyślnie przy tworzeniu StreamWriter bez podania parametrów zostanie użyte kodowanie UTF-8 z BOM (Byte Order Mark).

W praktyce to wygodne i nowoczesne, ale czasem trzeba jawnie ustawić kodowanie — np. dla zgodności ze starymi programami albo importowanymi danymi.

Jak ustawić kodowanie?

// Zapis pliku w kodowaniu Windows-1251 (cyrylica dla starych systemów)
using (var writer = new StreamWriter("cyrillic.txt", false, System.Text.Encoding.GetEncoding("windows-1251")))
{
    writer.WriteLine("Cześć, cyrylicki świecie!");
}

Ważny moment:
Kodowanie musi być obsługiwane przez system. Jeśli nie jesteś pewien — używaj UTF-8.

4. Dodatkowe parametry konstruktora

Zajrzyjmy do środka StreamWriter:

public StreamWriter(
    string path,              // ścieżka do pliku
    bool append = false,      // dopisywać na końcu pliku?
    Encoding encoding = null, // kodowanie
    int bufferSize = 1024     // rozmiar bufora, bajty
)
  • append — jeśli false (domyślnie), plik będzie nadpisany. Jeśli true, nowe wpisy zostaną dodane na końcu.
  • encoding — używane kodowanie.
  • bufferSize — rozmiar wewnętrznego bufora dla przyspieszenia pracy z dużą ilością danych.

Przykład: dopisywanie do tego samego pliku

// Plik będzie uzupełniany, a nie nadpisywany
using (var writer = new StreamWriter("log.txt", append: true))
{
    writer.WriteLine(DateTime.Now + " -- Nowe zdarzenie");
}

5. Przydatne niuanse

Co się dzieje przy dopisywaniu i nadpisywaniu

Tryb Co robi? Efekt w pliku
append: false Nadpisuje wszystko od nowa Stare dane są kasowane
append: true Dodaje nowe linie na końcu Stare linie zostają

Podpowiedź: Tryb dopisywania (append: true) — świetny wybór do logowania, gdy chcesz zachować historię zdarzeń.

StreamWriter i duże ilości danych

  • StreamWriter buforuje zapis: prawdziwe dane trafią do pliku trochę później, niż wywołasz WriteLine. Ale po zamknięciu strumienia (albo wywołaniu Flush()) wszystko na pewno znajdzie się na dysku.
  • Zapis przez WriteLine jest bardzo wydajny do wypisywania linia po linii. Do skomplikowanych formatów (JSON, CSV czy XML) lepiej użyć odpowiednich bibliotek, albo ostrożnie uciekać znaki specjalne (np. przecinki czy cudzysłowy).

Jak poprawnie zakończyć zapis i zwolnić zasoby

Poprawny sposób: zawsze używaj using!
Dzięki temu masz pewność, że plik się zamknie, nawet jeśli pojawi się wyjątek (np. jeśli nagle zabraknie miejsca na dysku albo plik zablokuje inny program).


flowchart TD
    A[Tworzenie StreamWriter] --> B[Praca z plikiem]
    B --> C{Wyjątek?}
    C -- Tak --> D[Dispose zostanie wywołany]
    C -- Nie --> D
    D --> E[Plik gwarantowanie zamknięty]
Schemat: using gwarantuje zamknięcie pliku nawet przy błędach

6. Praktyczne przykłady

Załóżmy, że w ramach kursu mamy mini-program do ewidencji książek. Dodajmy funkcjonalność zapisywania nowych książek do pliku.

Przykład: Zapisujemy nową książkę do osobnego pliku

using System;
using System.IO;

class Program
{
    static void Main()
    {
        Console.WriteLine("Podaj tytuł książki:");
        string bookTitle = Console.ReadLine();

        Console.WriteLine("Podaj autora:");
        string author = Console.ReadLine();

        string path = "books.txt";
        using (var writer = new StreamWriter(path, append: true))
        {
            writer.WriteLine($"{bookTitle};{author}");
            // Format CSV: każda linia - osobna książka, rozdzielone średnikiem
        }

        Console.WriteLine("Książka zapisana w pliku!");
    }
}

Spróbuj dodać kilka książek pod rząd. W pliku books.txt linie będą się dopisywać, nie kasując poprzednich. Takie zachowanie jest wygodne do prowadzenia logów lub dzienników — np. do twojego przyszłego systemu audytu, gdy już zostaniesz Enterprise-developerem.

7. Jak uniknąć typowych błędów przy zapisie do pliku

Większość początkujących spotyka się z takimi problemami:

Plik nie został zamknięty i jest zablokowany przez inny proces. Powód — zapomniałeś o using.

Zapisałeś dużo linii, ale plik został pusty: zapomniałeś wywołać Flush() albo zamknąć strumień (a z using robi się to automatycznie).

Niechcący nadpisałeś plik zamiast dopisać — zapomniałeś ustawić append: true.

Problemy z kodowaniem: plik otwiera się "krzaczki" w Notatniku — wybrane złe kodowanie albo inny program nie obsługuje UTF-8.

Wyjątki UnauthorizedAccessException albo DirectoryNotFoundException: program próbuje zapisać plik tam, gdzie nie ma uprawnień, albo do nieistniejącego folderu. Sprawdź ścieżkę i uprawnienia.

Błąd "file is used by another process": otworzyłeś plik do zapisu, ale go nie zamknąłeś, albo ktoś inny równolegle próbuje tam pisać.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION