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

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 12 Arbeiten mit Dateien und Streams
Pfeil 12.1 Einführung
Pfeil 12.2 Namespaces der Ein- bzw. Ausgabe
Pfeil 12.2.1 Das Behandeln von Ausnahmen bei E/A-Operationen
Pfeil 12.3 Laufwerke, Verzeichnisse und Dateien
Pfeil 12.3.1 Die Klasse »File«
Pfeil 12.3.2 Die Klasse »FileInfo«
Pfeil 12.3.3 Die Klassen »Directory« und »DirectoryInfo«
Pfeil 12.3.4 Die Klasse »Path«
Pfeil 12.3.5 Die Klasse »DriveInfo«
Pfeil 12.4 Die »Stream«-Klassen
Pfeil 12.4.1 Die abstrakte Klasse »Stream«
Pfeil 12.4.2 Die von »Stream« abgeleiteten Klassen im Überblick
Pfeil 12.4.3 Die Klasse »FileStream«
Pfeil 12.5 Die Klassen »TextReader« und »TextWriter«
Pfeil 12.5.1 Die Klasse »StreamWriter«
Pfeil 12.5.2 Die Klasse »StreamReader«
Pfeil 12.6 Die Klassen »BinaryReader« und »BinaryWriter«
Pfeil 12.6.1 Komplexe binäre Dateien

Galileo Computing - Zum Seitenanfang

12.4 Die »Stream«-KlassenZur nächsten Ü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:

  • 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.
  • 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

12.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 damit 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 12.11 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 12.12 definiert sind.

Tabelle 12.12 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 12.13).

Tabelle 12.13 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

12.4.2 Die von »Stream« abgeleiteten Klassen im ÜberblickZur 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 12.14 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 12.14 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

12.4.3 Die Klasse »FileStream«Zur nächsten ÜberschriftZur 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 12.2 und 12.3). Der Parameter vom Typ FileShare gibt an, ob ein gemeinsamer Zugriff auf die Datei möglich ist oder nicht (siehe auch Tabelle 12.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 Listing 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 = @"D:\Testfile.txt";
FileStream fs = new FileStream(path, FileMode.Create);
fs.Write(arr, 0, arr.Length);
fs.Close();
}

Listing 12.4 Schreiben in eine Datei mit einem »FileStream«-Objekt

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 12.15 Die Parameter der Methode »Write« eines »FileStream«-Objekts

Parameter Beschreibung

array

Ein Byte-Array, in das die übergebenen Daten gelesen werden

offset

Die Indexposition im Array, an der die Leseoperation beginnen soll

count

Die Anzahl der zu lesenden 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 Listings 12.4 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();
}

Listing 12.5 Lesen einer Datei mit einem »FileStream«-Objekt

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-Index, 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 12.3).

Abbildung

Abbildung 12.3 Der Positionszeiger in einem »Stream«-Objekt

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.

Kommen wir nun zur Lösung. FileStream beerbt die Klasse Stream und hat daher auch eine Seek-Methode, mit der wir den Positionszeiger beliebig einstellen 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 12.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 12\FileStreamSample

class Program {
static void Main(string[] args)
{
byte[] arr = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
string path = "D:\\Testfile.txt";

// Stream ö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++)
Console.WriteLine(arrRead[i]);
Console.ReadLine();

// FileStream schließen

fs.Close();
}
}

Listing 12.6 Das komplette Beispiel des Einsatzes der Klasse »FileStream«

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 dasjenige, 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

Obwohl es spezialisierte Klassen zum Lesen und Schreiben in eine Textdatei gibt, lässt sich das auch mit einem FileStream-Objekt realisieren. Wir wollen uns das im Folgenden ansehen:

// Beispiel: ..\Kapitel 12\TextdateiMitFileStream

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

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++)
Console.Write(Convert.ToChar(puffer[i]));
Console.ReadLine();
}
}

Listing 12.7 Textdatei mit einem »FileStream«-Objekt einlesen

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 grundsätzlich auch nichts einzuwenden. 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ürde 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# 2012

Visual C# 2012
Jetzt bestellen


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

 Buchempfehlungen
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


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
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.


[Rheinwerk Computing]

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