Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Über den Autor
Vorwort zur 4. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign und Vererbung
4 Weitere .NET-Datentypen
5 Weitere Möglichkeiten von C#
6 Projektmanagement und Visual Studio 2008
7 Fehlerbehandlung und Debugging
8 LINQ
9 Multithreading und asynchrone Methodenaufrufe
10 Arbeiten mit Dateien und Streams
11 Serialisierung
12 Einige wichtige .NET-Klassen
13 Grundlagen zum Erstellen einer Windows-Anwendung
14 Die wichtigsten Steuerelemente
15 Tastatur- und Mausereignisse
16 MDI-Anwendungen
17 Grafische Programmierung mit GDI+
18 Das Drucken (Printing)
19 Steuerelemente entwickeln
20 Programmiertechniken
21 WPF – die Grundlagen
22 Die Layoutcontainer
23 Die WPF-Controls
24 Konzepte von WPF
25 ADO.NET – die Verbindung zu einer Datenbank herstellen
26 Die Datenbankabfrage
27 Der SqlDataAdapter
28 Daten im lokalen Speicher – das DataSet
29 Eine Datenbank aktualisieren
30 Stark typisierte DataSets
31 Weitergabe von Anwendungen
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2008 von Andreas Kuehnel
Das umfassende Handbuch
Buch: Visual C# 2008

Visual C# 2008
geb., mit DVD
1.366 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1172-7
Pfeil 10 Arbeiten mit Dateien und Streams
Pfeil 10.1 Einführung
Pfeil 10.2 Namespaces der Ein- bzw. Ausgabe
Pfeil 10.2.1 Das Behandeln von Ausnahmen bei E/A-Operationen
Pfeil 10.3 Laufwerke, Verzeichnisse und Dateien
Pfeil 10.3.1 Die Klasse »File«
Pfeil 10.3.2 Die Klasse »FileInfo«
Pfeil 10.3.3 Die Klassen »Directory« und »DirectoryInfo«
Pfeil 10.3.4 Die Klasse »Path«
Pfeil 10.3.5 Die Klasse »DriveInfo«
Pfeil 10.3.6 Die Klasse »SpecialDirectories«
Pfeil 10.4 Die »Stream«-Klassen
Pfeil 10.4.1 Die abstrakte Klasse »Stream«
Pfeil 10.4.2 Die von »Stream« abgeleiteten Klassen im Überblick
Pfeil 10.4.3 Die Klasse »FileStream«
Pfeil 10.5 Die Klassen »TextReader« und »TextWriter«
Pfeil 10.5.1 Die Klasse »StreamWriter«
Pfeil 10.5.2 Die Klasse »StreamReader«
Pfeil 10.5.3 Die Klassen »StringWriter« und »StringReader«
Pfeil 10.6 Die Klassen »BinaryReader« und »BinaryWriter«
Pfeil 10.6.1 Komplexe binäre Dateien


Galileo Computing - Zum Seitenanfang

10.4 Die »Stream«-Klassen Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Stream ist die abstrahierte Darstellung eines Datenflusses aus einer geordneten Abfolge von Bytes. Welcher Natur dieser Datenstrom ist – ob er aus einer Datei stammt, ob er die Eingabe eines Benutzers an der Tastatur enthält oder ob er möglicherweise aus einer Netzwerkverbindung bezogen wird – bleibt zunächst einmal offen. Die Beschaffenheit des Datenflusses hängt nicht nur von Sender und Empfänger ab, sondern auch ganz entscheidend vom Betriebssystem.

Ein Entwickler soll seine Aufgabe unabhängig von diesen spezifischen Details lösen. E/A-Streams werden deshalb von Klassen beschrieben, die Allgemeingültigkeit garantieren. Das spielt insbesondere bei der Entwicklung von .NET-Anwendungen eine wesentliche Rolle, um die Plattformunabhängigkeit des Codes zu gewährleisten.

Streams dienen generell dazu, drei elementare Operationen ausführen zu können:

  • Dateninformationen müssen in einen Stream geschrieben werden. Nach welchem Muster das geschieht, wird durch den Typ des Streams vorgegeben.
  • Aus dem Datenstrom muss gelesen werden, ansonsten könnte man die Daten nicht weiterverarbeiten. Das Ziel kann unterschiedlich sein: Die Bytes können Variablen oder Arrays zugewiesen werden, sie könnten aber auch in einer Datenbank landen und zur Ausgabe an einem Peripheriegerät wie dem Drucker oder dem Monitor dienen.
  • Nicht immer ist es erforderlich, den Datenstrom vom ersten bis zum letzten Byte auszuwerten. Manchmal reicht es aus, erst ab einer bestimmten Position zu lesen. Man spricht dann vom wahlfreien Zugriff.
  • Nicht alle Datenströme können diese drei Punkte gleichzeitig erfüllen. Beispielsweise unterstützen Datenströme im Netzwerk nicht den wahlfreien Zugriff.
  • Bei den Streams werden grundsätzlich zwei Typen unterschieden:
Pfeil Base-Streams, die direkt aus einem Strom Daten lesen oder in diesen hineinschreiben. Diese Vorgänge können z. B. in Dateien, im Hauptspeicher oder in einer Netzwerkverbindung enden.
Pfeil Pass-Through-Streams ergänzen einen Base-Stream um spezielle Funktionalitäten. So können manche Streams verschlüsselt oder im Hauptspeicher gepuffert werden. Pass-Through-Streams lassen sich hintereinander in Reihe schalten, um so die Fähigkeiten eines Base-Streams zu erweitern. Auf diese Weise lassen sich sogar individuelle Streams konstruieren.

Galileo Computing - Zum Seitenanfang

10.4.1 Die abstrakte Klasse »Stream« Zur nächsten ÜberschriftZur vorigen Überschrift

Die Klasse Stream ist die abstrakte Basisklasse aller anderen Stream-Klassen. Sie stellt alle fundamentalen Eigenschaften und Methoden bereit, die von den abgeleiteten Klassen geerbt werden und letztendlich deren Funktionalität ausmachen.

Die von der Klasse Stream abgeleiteten Klassen unterstützen mit ihren Methoden nur Operationen auf Bytesequenzen. Da allein durch eine Bytesequenz noch keine Aussage darüber getroffen ist, welcher Datentyp sich hinter mehreren aufeinanderfolgenden Bytes verbirgt, muss der Inhalt eines solchen Stroms noch interpretiert werden.

Die Eigenschaften der Klasse »Stream«

Streams stellen Schreib-, Lese- und Suchoperationen bereit. Allerdings unterstützt nicht jeder Stream gleichzeitig alle Operationen. Um in einem gegebenen Stream dessen Verhaltensweisen festzustellen, können Sie die Eigenschaften CanRead, CanWrite und CanSeek abfragen, die einen booleschen Wert zurückliefern und Auskunft über die Charakteristik dieses Stream-Objekts liefern. Die Eigenschaft Length liefert die Länge des Streams und Position die aktuelle Position innerhalb des Streams. Letztere wird allerdings nur von den Streams bereitgestellt, die auch die Positionierung mit der Seek-Methode unterstützen.


Tabelle 10.12 Eigenschaften der abstrakten Klasse »Stream«

Eigenschaft Beschreibung

CanRead

Ruft in einer abgeleiteten Klasse einen Wert ab, der angibt, ob der aktuelle Stream Lesevorgänge unterstützt.

CanWrite

Ruft in einer abgeleiteten Klasse einen Wert ab, der angibt, ob der aktuelle Stream Schreibvorgänge unterstützt.

CanSeek

Ruft in einer abgeleiteten Klasse einen Wert ab, der angibt, ob der aktuelle Stream Suchvorgänge unterstützt.

Length

Ruft in einer abgeleiteten Klasse die Länge des Streams in Byte ab.

Position

Ruft in einer abgeleiteten Klasse die Position im aktuellen Stream ab oder legt diese fest.


Die Methoden der Klasse »Stream«

Die wichtigsten Methoden aller Stream-Klassen dürften Read, Write und Seek sein. Sehen wir uns zunächst die Definitionen der beiden Methoden Read und Write an, die von jeder abgeleiteten Klasse überschrieben werden müssen.

public abstract int Read(in byte[] buffer,int offset,int count); 
public abstract void Write(byte[] buffer, int offset, int count);

Einem schreibenden Stream müssen Sie die Daten übergeben, die in den Datenstrom geschrieben werden sollen. Die Write-Methode benutzt dazu den ersten Parameter, liest die Elemente byteweise ein und schreibt sie in den Strom. Der Empfänger des Datenstroms kann die Bytes mit Read im ersten Parameter entnehmen. Der zweite Parameter offset bestimmt die Position im Array, ab der der Lese- bzw. Schreibvorgang beginnen soll. Meistens wird hier die Zahl 0 eingetragen, d. h., die Operation greift auf das erste Array-Element zu – entweder lesend oder schreibend. Im dritten und letzten Parameter wird angegeben, wie viele Bytes gelesen oder geschrieben werden sollen.

Beachten Sie auch, dass Write als Methode ohne Rückgabewert implementiert ist, während Read einen int liefert, dem Sie die Anzahl der gelesenen Bytes entnehmen können, die in den Puffer – also das Array – geschrieben worden sind. Der Rückgabewert ist 0, wenn das Ende des Streams erreicht ist. Er kann aber auch kleiner sein als im dritten Parameter angegeben, wenn weniger Bytes im Stream eingelesen werden.

Die abstrakte Klasse Stream definiert zwei weitere, ähnliche Methoden, die jedoch jeweils nur immer ein Byte aus dem Datenstrom lesen oder in diesen hineinschreiben: ReadByte und WriteByte. Beide Methoden sind parameterlos und setzen den Positionszeiger innerhalb des Streams um eine (Byte-)Position weiter.

public virtual int ReadByte(); 
public virtual void WriteByte(byte value);

Der Rückgabewert der ReadByte-Methode ist –1, wenn das Ende des Datenstroms erreicht ist.

Um in einem Datenstrom ab einer vorgegebenen Position zu lesen oder zu schreiben, bietet sich die Seek-Methode an:

public abstract long Seek(long offset, SeekOrigin origin);

Mit den beiden Parametern offset und origin wird der Startpunkt für den Positionszeiger im Stream festgelegt, ab dem weitere E/A-Operationen aktiv werden. offset beschreibt die Verschiebung in Bytes ab der unter origin festgelegten Ursprungsposition. origin ist vom Typ der Aufzählung SeekOrigin, in der die drei Konstanten aus Tabelle 10.13 definiert sind.


Tabelle 10.13 Konstantenliste der Aufzählung »SeekOrigin«

Member Beschreibung

Begin

Gibt den Anfang eines Streams an.

Current

Gibt die aktuelle Position innerhalb eines Streams an.

End

Gibt das Ende eines Streams an.


Mit SeekOrigin.Begin wird der Positionszeiger auf das erste Byte des Datenstroms gesetzt, mit SeekOrigin.Current behält er seine augenblickliche Position bei, und mit SeekOrigin.End wird er auf das Byte gesetzt, das als Erstes den Bytes des vollständigen Streams folgt. Ausgehend von origin wird durch Addition von offset die gewünschte Startposition ermittelt.

Ein Stream, der einmal geöffnet worden ist und Daten in den Puffer geschrieben hat, sollte ordnungsgemäß mit Close geschlossen werden.

Sie haben jetzt so viele Methoden im Schnelldurchlauf kennengelernt, dass alle erwähnten noch einmal in übersichtlicher tabellarischer Form zusammengefasst werden sollen (siehe Tabelle 10.14).


Tabelle 10.14 Methoden der abstrakten Klasse »Stream«

Methode Beschreibung

Close

Schließt den aktuellen Stream und gibt alle dem aktuellen Stream zugeordneten Ressourcen frei.

Read

Liest eine Folge von Bytes aus dem aktuellen Stream und setzt den Datenzeiger im Stream um die Anzahl der gelesenen Bytes weiter.

ReadByte

Liest ein Byte aus dem Stream und erhöht die Position im Stream um ein Byte. Der Rückgabewert ist –1, wenn das Ende des Streams erreicht ist.

Seek

Legt die Position im aktuellen Stream fest.

Write

Schreibt eine Folge von Bytes in den aktuellen Stream und erhöht den Datenzeiger im Stream um die Anzahl der geschriebenen Bytes.

WriteByte

Schreibt ein Byte an die aktuelle Position im Stream und setzt den Datenzeiger um eine Position im Stream weiter.



Galileo Computing - Zum Seitenanfang

10.4.2 Die von »Stream« abgeleiteten Klassen im Überblick Zur nächsten ÜberschriftZur vorigen Überschrift

Sie haben in den vorherigen Ausführungen nur die wichtigsten Methoden und Eigenschaften der Klasse Stream kennengelernt. Die bisherigen Aussagen sollten genügen, um eine Vorstellung davon zu erhalten, welche wesentlichen Verhaltensweisen an die abgeleiteten Klassen weitervererbt werden.

Den in Tabelle 10.15 aufgeführten Klassen dient Stream als Basisklasse. Dabei ist die Tabelle nicht vollständig, sondern beinhaltet nur die wichtigsten Typen. Beachten Sie bitte, dass die verschiedenen ableitenden Klassen nicht alle demselben Namespace angehören.


Tabelle 10.15 Die von »Stream« abgeleiteten Klassen

Stream-Typ Beschreibung

BufferedStream

Die Klasse BufferedStream wird benutzt, um Daten eines anderen E/A-Datenstroms zu puffern. Ein Puffer ist ein Block von Bytes im Arbeitsspeicher des Rechners, der dazu benutzt wird, den Datenstrom zu cachen, um damit die Anzahl der Aufrufe an das Betriebssystem zu verringern. Dadurch lässt sich insgesamt die Effizienz verbessern. Diese Klasse wird immer im Zusammenhang mit anderen Klassen eingesetzt.

CryptoStream

Daten, die nicht in ihrem Originalzustand in einen Strom geschrieben werden sollen, lassen sich mit der Klasse CryptoStream verschlüsseln. CryptoStream wird immer zusammen mit einem anderen Stream kombiniert.

FileStream

Diese Klasse wird dazu benutzt, um Daten in Dateien des Dateisystems zu schreiben. Eine Netzwerkverbindung kann ebenfalls das Ziel dieses Datenstroms sein.

GZipStream

Mit den Methoden dieser Klasse können Sie Byteströme komprimieren und dekomprimieren.

MemoryStream

Meistens sind Dateien oder Netzwerkverbindungen das Ziel der Datenströme. Es kann jedoch auch sinnvoll sein, Daten bewusst temporär in den Hauptspeicher zu schreiben und sie später von dort wieder zu lesen. Viele Anwendungen arbeiten nach dem Prinzip, Daten in eine temporäre Datei zu speichern. Ein MemoryStream kann temporäre Dateien ersetzen und trägt damit zur Steigerung der Leistungsfähigkeit einer Anwendung bei, da das Schreiben und Lesen in den Hauptspeicher um ein Vielfaches schneller ist als das Schreiben auf die Festplatte.

NetworkStream

Ein Datenfluss, der auf der Klasse NetworkStream basiert, sendet die Daten basierend auf Sockets. Das Besondere an diesem Datenstrom ist, dass er nur die Fähigkeit hat, Daten vollständig in den Strom zu schreiben oder aus diesem zu lesen. Der Zugriff auf beliebige Daten innerhalb des Stroms ist nicht möglich.



Galileo Computing - Zum Seitenanfang

10.4.3 Die Klasse »FileStream« topZur vorigen Überschrift

Die Klasse FileStream ist die universellste Klasse und erscheint damit in vielen Anwendungsfällen am geeignetsten. Sie hat die Fähigkeit, sowohl byteweise aus einer Datei zu lesen als auch byteweise in eine Datei zu schreiben. Außerdem kann ein Positionszeiger auf eine beliebige Position innerhalb des Streams gesetzt werden. Ein FileStream puffert die Daten, um die Ausführungsgeschwindigkeit zu erhöhen. Die Größe des Puffers beträgt standardmäßig 8 KByte.

Die FileStream-Klasse bietet eine Reihe von Konstruktoren an, um dem Objekt bestimmte Verhaltensweisen und Eigenschaften mit auf den Lebensweg zu geben:

public FileStream (string, FileMode); 
public FileStream (string, FileMode, FileAccess); 
public FileStream (string, FileMode, FileAccess, FileShare); 
public FileStream (string, FileMode, FileAccess, FileShare, int); 
public FileStream (string, FileMode, FileAccess, FileShare, int, bool);

Sie können ein FileStream-Objekt erzeugen, indem Sie im ersten Parameter eine Pfadangabe als Zeichenfolge übergeben. Der Parameter FileMode beschreibt, wie das Betriebssystem die Datei öffnen soll (FileMode.Append, FileMode.Create, FileMode.CreateNew …). FileAccess hingegen gibt an, wie auf die Datei zugegriffen werden darf (FileAccess.Read, FileAccess.Write oder FileAccess.ReadWrite). Sie haben diese Typen bereits im Abschnitt zur Klasse File kennengelernt (siehe auch die Tabellen 10.2 und 10.3). Der Parameter vom Typ FileShare gibt an, ob ein gemeinsamer Zugriff auf die Datei möglich ist oder nicht (siehe auch Tabelle 10.4).

Der Puffer, in den ein FileStream die Daten zur Steigerung der Leistungsfähigkeit schreibt, ist standardmäßig 8 KByte groß. Mit dem Parameter vom Typ int können Sie die Größe des Puffers bei der Instanziierung beeinflussen. Mit dem letzten Parameter vom Typ bool kann noch angegeben werden, ob das Objekt asynchrone Zugriffe unterstützen soll.

Das Schreiben in einen »FileStream«

Das folgende Codefragment demonstriert, wie mit einem FileStream-Objekt Daten in eine Datei geschrieben werden:

static void Main(string[] args) { 
  byte[] arr = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; 
  string path = @"C:\Testfile.txt"; 
  FileStream fs = new FileStream(path, FileMode.Create); 
  fs.Write(arr, 0, arr.Length); 
  fs.Close(); 
}

Zunächst wird ein Byte-Array deklariert und mit insgesamt zehn Zahlen initialisiert. In der zweiten Anweisung wird der Name der Datei festgelegt, in die das Array geschrieben werden soll.

Bei der Instanziierung des FileStream-Objekts werden dem Konstruktor im ersten Argument der Pfad und die Datei bekannt gegeben, auf der der Stream operieren soll. Die Fähigkeiten dieses Streams beschreibt das zweite Argument: Die Konstante FileMode.Create teilt dem Konstruktor mit, dass das FileStream-Objekt eine neue Datei erzeugen kann oder, falls im angegebenen Pfad bereits eine gleichnamige Datei existiert, diese überschreiben soll. Mit

fs.Write(arr, 0, arr.Length);

wird der Inhalt des Arrays arr dem Stream-Objekt übergeben. Die Syntax der Methode Write der Klasse FileStream lautet wie folgt:

public void Write(byte[] array, int offset, int count);

Dabei haben die drei Parameter die folgende Bedeutung:


Tabelle 10.16 Die Parameter der Methode »Write« eines »FileStream«-Objekts

Parameter Beschreibung
array

Ein Byte-Array, das in den Stream geschrieben werden soll

offset

Die Indexposition im Array, an der die Schreibeoperation beginnen soll

count

Die Anzahl der zu schreibenden Bytes


Der Schreibvorgang des Beispiels startet mit dem ersten Array-Element. Das sagt der zweite Parameter der Write-Methode aus. Die Anzahl der zu schreibenden Bytes bestimmt der dritte Parameter – in unserem Beispiel werden alle Array-Elemente dem Datenstrom zugeführt. Zum Schluss wird der FileStream mit der Methode Close geschlossen.

Das Lesen aus einem »FileStream«

Wir wollen uns nun auch vom Erfolg unserer Bemühungen überzeugen und die Datei auswerten. Dazu wird der Programmcode des Beispiels wie folgt ergänzt:

static void Main(string[] args) { 
  ... 
  byte[] arrRead = new byte[10]; 
  fs.Read(arrRead, 0, 10); 
  for (int i = 0; i < arr.Length; i++) 
    Console.WriteLine(arrRead[i]); 
  fs.Close(); 
}

Wir deklarieren ein weiteres Array (arrRead), in das wir das Ergebnis der Leseoperation hineinschreiben. Da uns bekannt ist, wie viele Byte-Elemente sich in unserer Datei befinden (wie unfair), können wir die Array-Grenze schon im Voraus festlegen.

Nun kommt es zum Aufruf der Read-Methode. Zuerst wollen wir uns wieder die Syntax dieser Methode anschauen:

public override int Read(in byte[] array, int offset, int count);

Die Parameter sind denen der Write-Methode sehr ähnlich. Das FileStream-Objekt, auf dem die Read-Methode aufgerufen wird, repräsentiert eine bestimmte Datei. Diese wurde bereits über den Konstruktor bekannt gegeben. Aus der Datei werden die Daten in das Array eingelesen, das durch den Parameter array beschrieben wird. Der erste Array-Instex, der der Schreiboperation zur Verfügung steht, wird im Parameter offset angegeben, die Anzahl der aus dem FileStream zu lesenden Bytes im dritten Parameter count.

Wir wollen den ersten Byte-Wert aus dem Datenstrom in das mit 0 indizierte Element des Arrays arrRead schreiben und geben das im zweiten Parameter bekannt. Die Gesamtanzahl der zu lesenden Bytes teilen wir dem dritten Parameter mit. In einer Schleife wird danach das eingelesene Array durchlaufen und an der Konsole ausgegeben.

Starten Sie nun die Laufzeit des Programms, wird die Enttäuschung groß sein und Sie an den eigenen Programmierfähigkeiten zweifeln lassen! Denn bedauerlicherweise werden nicht die Zahlenwerte ausgegeben, die wir in die Datei geschrieben haben, sondern nur Nullen. Haben wir etwas falsch gemacht, und wenn ja, wie ist das zu erklären?

Der Positionszeiger

Die Schreib- bzw. Leseposition in einem Datenstrom wird durch einen Positionszeiger beschrieben. Schließlich muss der Strom wissen, auf welchem Byte die folgende Operation ausgeführt werden soll. Bei der Instanziierung eines Stream-Objekts verweist der Zeiger zunächst auf das erste Byte im Stream. Mit dem Aufruf der Write-Methode wird ein Wert daher genau an diese Position geschrieben. Anschließend wird der Positionszeiger auf die folgende Byte-Position verschoben.

Dieser Vorgang wiederholt sich bei jedem Schreibvorgang, von denen es in unserem Beispiel zehn gibt, nämlich für jedes zu schreibende Array-Element einen. Am Ende, wenn wir unsere zehn Bytes in den Strom geschrieben haben, verweist der Positionszeiger auf das folgende, nun elfte Byte im Stream (siehe Abbildung 10.3). Genau das verursacht nun ein Problem. Wir rufen auf dem FileStream-Objekt die Read-Methode auf und lesen ab der Position, die aktuell durch den Datenzeiger beschrieben wird. Das ist aber die elfte Stelle im Datenstrom – und nicht die erste, wie wir es eigentlich erwartet haben bzw. wie es hätte sein sollen.

Abbildung 10.3 Der Positionszeiger in einem »Stream«-Objekt

Kommen wir nun zur Lösung. FileStream beerbt die Klasse Stream und hat daher auch eine Seek-Methode, mit der wir den Positionszeiger beliebig verbiegen können:

public override long Seek(long offset, SeekOrigin origin);

Wir überlegen uns, wohin wir den Ursprung des Positionszeigers verlegen wollen – natürlich an den Anfang des Datenstroms. Also muss der zweite Parameter der Seek-Methode auf

origin = SeekOrigin.Begin

festgelegt werden (siehe dazu auch Tabelle 10.13). Nun geben wir im ersten Argument den tatsächlichen und endgültigen Startpunkt des Positionszeigers bezogen auf den im zweiten Parameter definierten Ursprung an. Er lautet 0, denn schließlich wollen wir den Zeiger auf die erste Position des Datenstroms setzen.

... 
fs.Write(arr, 0, arr.Length); 
... 
fs.Seek(0, SeekOrigin.Begin); 
fs.Read(arrRead, 0, 10); 
...

Natürlich wäre es auch möglich, zunächst das FileStream-Objekt zu schließen und es danach neu zu instanziieren. Damit hätten wir wieder einen Positionszeiger, der auf das erste Byte im Stream zeigt.

Wenn wir nach der Ergänzung mit Seek das Programm noch einmal starten, wird das Ergebnis wie erwartet ausgegeben.

Zum Schluss wollen wir noch einmal den gesamten Code zusammenfassen.

// ---------------------------------------------------------
// Beispiel: ...\Kapitel 10\FileStreamDemo
// -------------------------------------------------------------
class Program { 
  static void Main(string[] args) { 
    byte[] arr = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; 
    string path = "C:\\Testfile.txt"; 
    // FileStream öffnen 
    FileStream fs = new FileStream(path, FileMode.Create); 
    // in den Stream schreiben 
    fs.Write(arr, 0, arr.Length); 
    byte[] arrRead = new byte[10]; 
    // Positionszeiger auf den Anfang des Streams setzen 
    fs.Seek(0, SeekOrigin.Begin); 
    // Stream lesen 
    fs.Read(arrRead, 0, 10); 
    for (int i = 0; i < arr.Length; i++)_$ret_      Console.WriteLine(arrRead[i]); 
    Console.ReadLine(); 
    // FileStream schließen 
    fs.Close(); 
  } 
}

Wir müssen nicht zwangsläufig das komplette Byte-Array vom ersten bis zum letzten Element in die Datei schreiben. Mit

fs.Write(arr, 2, arr.Length - 2);

ist das erste zu schreibende Element das, das die Zahl 30 enthält (entsprechend dem Index 2). Wenn wir allerdings den dritten Parameter, der die Anzahl der zu lesenden Bytes angibt, nicht entsprechend anpassen, wird über das Ende des Arrays hinaus gelesen, was zu der Exception ArgumentException führt.

Eine Textdatei mit »FileStream« lesen

In Abschnitt 10.3.1 haben Sie anhand eines Beispiels gesehen, wie eine Textdatei eingelesen werden kann. In dem Beispiel hatten wir dazu die Klasse StreamReader benutzt. Auch mit einem FileStream-Objekt kann problemlos auf eine Textdatei zugegriffen werden. Wir wollen uns das im Folgenden ansehen:

// ---------------------------------------------------------
// Beispiel: ...\Kapitel 10\FileStreamTextdatei
// -------------------------------------------------------------
class Program { 
  static void Main(string[] args) { 
    // Benutzereingabe anfordern 
    Console.Write("Geben Sie die zu öffnende Datei an: ");; 
    string strFile = Console.ReadLine(); 
    // prüfen, ob die angegebene Datei existiert 
    if (! File.Exists(strFile)) { 
      Console.WriteLine("Die Datei {0} existiert nicht!", strFile); 
      Console.ReadLine(); 
      return; 
    }

    // Datei öffnen 
    FileStream fs = File.Open(strFile, FileMode.Open);

    // Byte-Array, in das die Daten aus dem Datenstrom eingelesen werden_$ret_    byte[] puffer = new Byte[fs.Length];

    // die Zeichen aus der Datei lesen und in das Array 
    // schreiben, der Lesevorgang beginnt mit dem ersten Zeichen 
    fs.Read(puffer, 0, (int)fs.Length);

    // das Byte-Array elementweise einlesen und jedes 
    // Array-Element in Char konvertieren 
    for (int i = 0; i < fs.Length; i++)_$ret_      Console.Write(Convert.ToChar(puffer[i])); 
    Console.ReadLine(); 
  } 
}

Nach dem Start der Laufzeitumgebung wird der Benutzer dazu aufgefordert, den Pfad zu einer Textdatei anzugeben. Diesmal beschreiten wir allerdings einen anderen Weg und rufen den Konstruktor nicht direkt auf, sondern die Methode Open der Klasse File:

FileStream fs = File.Open(strFile, FileMode.Open);

Diese Anweisung funktioniert tadellos, weil der Rückgabewert der File.Open-Methode die Referenz auf eine FileStream-Instanz liefert. Gegen den Weg über einen FileStream-Konstruktor, der diese Möglichkeit auch bietet, ist zwar grundsätzlich nichts einzuwenden, wir wollen uns allerdings die gezeigte Alternative vor Augen führen.

Im folgenden Schritt wird das Byte-Array puffer deklariert und mit einer Kapazität initialisiert, die der Größe der Datei entspricht:

byte[] puffer = new Byte[fs.Length];

Die Größe der Datei besorgen wir uns mit der Eigenschaft Length der Klasse FileStream, die uns die Größe des Datenstroms liefert. Daran schließt sich die Leseoperation mit Read an, die den Inhalt der Textdatei byteweise liest und in das Array puffer schreibt:

fs.Read(puffer, 0, (int)fs.Length);

Weil der dritte Parameter der Read-Methode ein Datum vom Typ int erwartet und die Eigenschaft Length einen long liefert, müssen wir noch in den richtigen Datentyp konvertieren.

Ein FileStream-Objekt arbeitet grundsätzlich nur auf der Basis von Bytes, es weiß nichts von dem tatsächlichen Typ, der sich im Datenstrom verbirgt. Eine Textdatei enthält aber Zeichen, die, als ANSI-Zeichen interpretiert, erst den wirklichen Informationsgehalt liefern. Deshalb müssen wir jedes einzelne Byte des Streams in einen char-Typ konvertieren. Das geschieht in einer Schleife, die alle Bytes des Arrays abgreift, konvertiert und danach an der Konsole ausgibt.

for (int i = 0; i < fs.Length; i++) 
  Console.Write(Convert.ToChar(puffer[i]));

Würden den Daten aus der Datei ein anderer Typ zugrunde liegen, beispielsweise int oder float, müsste dieser Zieldatentyp angegeben werden. Wissen Sie nicht, welcher Typ in der Datei gespeichert ist, können Sie mit dem Inhalt praktisch nichts anfangen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2008
Visual C# 2008
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Visual C# 2012






 Visual C# 2012


Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern