CodeGym /Kurse /C# SELF /Ein- und Ausgabeströme: St...

Ein- und Ausgabeströme: Stream

C# SELF
Level 36, Lektion 1
Verfügbar

1. Einführung

Stell dir einen Wasserkocher mit Wasser vor. Du drehst den Hahn auf – das Wasser beginnt zu fließen. Du kannst viel Wasser auf einmal nehmen und alles auf einmal eingießen, oder du füllst den Wasserkocher Stück für Stück. Genauso ist es mit Dateien – es ist nicht immer praktisch oder möglich, die ganze Datei sofort in den Speicher zu laden. Dateien können groß sein, und manchmal ist die Datenquelle gar keine Datei, sondern zum Beispiel eine Netzwerkverbindung, bei der die Daten nach und nach ankommen.

Wenn wir immer nur mit Byte-Arrays arbeiten würden, wäre bei großen Dateien unser Speicher schnell voll, und für "endlose" Datenströme (wie Video- oder Audiostreams) würde das gar nicht funktionieren. Genau hier hilft das Konzept des Streams!

In .NET ist ein Stream eine Abstraktion für den sequentiellen Zugriff auf Daten: Egal, was die Quelle ist – Datei, Netzwerk, Speicher oder sogar etwas ganz Exotisches wie ein komprimiertes Archiv. Ein Stream erlaubt dir, Daten stückweise zu lesen und zu schreiben, meistens in Blöcken oder Bytes.

Die Hauptidee:

  • Stream – das ist ein Kanal für die Datenübertragung. Er ist wie ein Fließband: Du kannst Daten "ablegen" (schreiben) oder "abholen" (lesen), ohne dich direkt um die Details zu kümmern, wo und wie sie gespeichert werden.
  • Daten kommen nacheinander: Du kannst das nächste Stück erst lesen, nachdem du das vorherige gelesen hast (oder umgekehrt, wenn Spulen unterstützt wird).
  • In den meisten Fällen speicherst du nicht alle Daten auf einmal im Speicher (und dein Computer wird es dir danken).

Diese Abstraktion ist die Grundlage fast aller Ein- und Ausgabeoperationen in .NET: Arbeit mit Dateien, Netzwerken, Archiven, sogar mit der Konsole!

2. Streams System.IO.Stream

Vererbung und Architektur: System.IO.Stream

Fast alle Streams in .NET erben von der abstrakten Klasse System.IO.Stream. Sie definiert die wichtigsten Methoden zum Lesen, Schreiben, Navigieren und Verwalten des Streams.


classDiagram
    class Stream {
        +Read()
        +Write()
        +Seek()
        +CanRead
        +CanWrite
        +CanSeek
        +Length
        +Position
    }
    class FileStream
    class MemoryStream
    class NetworkStream
    class CryptoStream
    Stream <|-- FileStream
    Stream <|-- MemoryStream
    Stream <|-- NetworkStream
    Stream <|-- CryptoStream
Vererbungsschema der Streams in .NET
  • Stream – die abstrakte Basisklasse
  • FileStream – für die Arbeit mit Dateien
  • MemoryStream – für die Arbeit mit Daten im Speicher
  • NetworkStream – für Netzwerkkommunikation
  • CryptoStream – für Verschlüsselung/Entschlüsselung

Kurzüberblick über die wichtigsten Eigenschaften und Methoden eines Streams

Eigenschaft / Methode Beschreibung
CanRead
Ob aus diesem Stream gelesen werden kann
CanWrite
Ob in diesen Stream geschrieben werden kann
CanSeek
Ob man im Stream navigieren kann (wird nicht von allen unterstützt)
Length
Länge des Streams (wenn unterstützt – nicht bei allen Streams)
Position
Aktuelle Position im Stream
Read(...)
Daten lesen
Write(...)
Daten schreiben
Seek(...)
Im Stream navigieren
Flush()
Puffer leeren (alles Gesammelte in den Stream schreiben)
Close()
/
Dispose()
Stream schließen und Ressourcen freigeben

Schauen wir uns an, wie das "in der Praxis" aussieht.

3. Beispiel: Dateien lesen und schreiben mit Stream

Hier ein ziemlich minimales Beispiel, um einen Stream "in Aktion" zu sehen:


// Datei zum Schreiben öffnen
using var stream = new FileStream("numbers.bin", FileMode.Create);

// Stell dir vor, wir wollen die Zahlen von 1 bis 10 in die Datei schreiben
for (int i = 1; i <= 10; i++)
{
    byte val = (byte)i;
    stream.WriteByte(val); // Wir schreiben jeweils ein Byte
}

// Datei explizit schließen, um sie gleich zum Lesen zu öffnen
stream.Close(); 

// Jetzt versuchen wir, diese Zahlen wieder einzulesen
using var stream2 = new FileStream("numbers.bin", FileMode.Open);
int value;
while ((value = stream2.ReadByte()) != -1)
{
    Console.WriteLine(value); // Gibt 1, 2, ... 10 aus
}

Hier verwenden wir FileStream, der ein echter Stream im vollen Sinne ist: Du liest und schreibst Daten blockweise oder byteweise.

Arten von Streams: Wo können sie vorkommen?

Ein Stream ist nicht unbedingt nur eine Datei auf der Festplatte. Hier ein paar Beispiele, wo das Stream-Konzept verwendet wird:

  • Datei auf der Festplatte (zum Beispiel FileStream – der häufigste Fall)
  • Stream im Arbeitsspeicher (MemoryStream – praktisch für temporäre oder Zwischendaten)
  • Netzwerkverbindung (NetworkStream)
  • Komprimierung/Archivierung (GZipStream, DeflateStream)
  • Verschlüsselung (CryptoStream)
  • Konsolen-Ein-/Ausgabe (ja, wirklich!) – technisch gesehen auch Streams

Das erlaubt dir, Code zu schreiben, ohne dich um die konkrete Datenquelle oder das Ziel zu kümmern: Wenn dein Code mit einem Stream arbeitet, ist er universell!

4. Nützliche Feinheiten

Lesen und Schreiben sind Operationen, bei denen Daten stückweise übertragen werden. Meistens über Byte-Arrays und die Methoden Read, Write.

Beispiel: Datei blockweise lesen

byte[] buffer = new byte[1024]; // Puffer mit 1024 Bytes (1 KB)
using var stream = new FileStream("bigfile.bin", FileMode.Open);

int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
    // Verarbeite nur bytesRead Bytes im buffer
    int sum = 0;
    for (int i = 0; i < bytesRead; i++)
        sum += buffer[i];

    Console.WriteLine($"Summe des Blocks: {sum}");
}

Dieser Ansatz wird überall verwendet – von Virenscannern bis zu Musikplayern.

Positionieren im Stream (Position, Seek)

In den meisten Stream-Implementierungen (zum Beispiel bei Dateien) kannst du dich durch die Daten bewegen – du liest nicht einfach das "nächste Stück", sondern kannst zu einer bestimmten Position springen und von dort aus arbeiten.

using var stream = new FileStream("numbers.bin", FileMode.Open);
stream.Position = 5; // Springe zum 6. Byte (Nummerierung ab 0)
int value = stream.ReadByte();
Console.WriteLine($"6. Byte in der Datei: {value}");

Streams können nur zum Lesen, nur zum Schreiben oder für beides sein

Manche Streams unterstützen nur eine der Varianten:

  • Datei, die zum Schreiben geöffnet wurde: nur Write()
  • Stream zum Lesen von Netzwerkdaten: nur Read()
  • In manchen exotischen Fällen (z.B. Stream für Druckerausgabe) ist "Zurückspulen" oder Positionieren gar nicht möglich.

Prüfe die unterstützten Operationen mit den Eigenschaften CanRead, CanWrite, CanSeek:

using var stream = new FileStream("myfile.txt", FileMode.OpenOrCreate);
if (stream.CanRead)
    Console.WriteLine("Lesen wird unterstützt");
if (stream.CanWrite)
    Console.WriteLine("Schreiben wird unterstützt");
if (stream.CanSeek)
    Console.WriteLine("Navigieren in der Datei ist möglich");

Pufferung in Streams

Fast alle Streams verwenden interne Puffer, um die Performance zu verbessern. Pufferung spart Zugriffe auf Festplatte/Netzwerk: Die Daten werden intern gesammelt und dann auf einmal gelesen/geschrieben.

Die Methode Flush() leert den Puffer (zum Beispiel, um sicherzustellen, dass alles auf die Festplatte geschrieben wurde):

using var stream = new FileStream("log.txt", FileMode.Append);
byte[] bytes = Encoding.UTF8.GetBytes("Hallo, Stream!\n");
stream.Write(bytes, 0, bytes.Length);
stream.Flush(); // Garantiert, dass die Daten wirklich auf der Festplatte sind

Wenn du lebenswichtige Daten schreibst (zum Beispiel Zahlungstransaktionen!), ist Flush() dein Freund.

5. Typische Fehler beim Arbeiten mit Streams

Sehr oft machen Anfänger folgende Fehler:

Sie vergessen, den Stream zu schließen (und bekommen Speicherlecks, "hängende" Dateien und anderen Spaß).

Sie verwechseln Text- und Binärstreams – versuchen, einen String mit einer Byte-Methode zu schreiben, und bekommen dann "Kauderwelsch".

Sie verwenden einen zu kleinen Puffer (oder gar keinen) – die Operationen werden langsam.

Sie denken, dass Read() immer genau so viele Bytes liest, wie angefordert – tatsächlich kann es weniger sein; du musst immer den Rückgabewert prüfen.

Sie beachten nicht, dass nicht alle Streams das Navigieren (Seek) unterstützen, besonders Netzwerkstreams.

Zum Beispiel:


// Schlechtes Beispiel: Alle Bytes der Datei lesen, ohne zu prüfen, wie viele tatsächlich gelesen wurden
byte[] buffer = new byte[1024];
using (var stream = new FileStream("data.bin", FileMode.Open))
{
    int bytesRead = stream.Read(buffer, 0, 1024);
    // bytesRead kann kleiner als 1024 sein, wenn die Datei kleiner ist!
}
2
Aufgabe
C# SELF, Level 36, Lektion 1
Gesperrt
Überprüfung der Eigenschaften eines Streams
Überprüfung der Eigenschaften eines Streams
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION