7.4 Datenflüsse in Streams
Ein Stream ist die abstrahierte Darstellung eines Datenflusses aus einer geordneten Abfolge von Bytes. Woher die Daten kommen und wohin sie gehen, ist nicht wesentlich für einen Stream und hängt vom Sender, Empfänger und Betriebssystem ab. Durch die Verwendung von Streams werden Ihre Programme durch die Abstraktion plattform- und datenkanalunabhängig. Streams führen drei elementare Operationen aus:
- Dateninformationen müssen in einen Sream geschrieben werden. Nach welchem Muster das geschieht, wird durch den Typ des Streams vorgegeben.
- Aus dem Datenstrom muss gelesen werden. Es gibt viele Ziele: Dateien, Netzwerk, Variablen, Datenbanken, Drucker, Monitor usw.
- Nicht immer muss der gesamte Datenstrom ausgewertet werden. Manchmal reicht es aus, 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 lesen oder in ihn hineinschreiben. Die Vorgänge können z. B. in Dateien, im Hauptspeicher oder einer Netzwerkverbindung enden.
- Pass-Through-Streams basieren auf einem anderen Stream und kommunizieren nicht direkt mit den Enden. Sie haben spezielle Funktionalitäten, zum Beispiel Verschlüsselung oder Pufferung. Pass-Through-Streams lassen sich beliebig in Reihe schalten.
7.4.1 Die abstrakte Klasse Stream
Die Klasse Stream ist die abstrakte Basisklasse aller anderen Streamklassen und enthält deren Basisfunktionalität. Die von der Klasse Stream abgeleiteten Klassen unterstützen nur Operationen auf Bytesequenzen, die noch keinen Typ festlegen und noch interpretiert werden müssen.
Eigenschaften
Streams stellen Schreib-, Lese- und Suchoperationen bereit. Ob ein konkreter Stream eine Operation unterstützt, können Sie mit den Eigenschaften CanRead, CanWrite und CanSeek prüfen. 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 7.13 fasst die Eigenschaften von Stream zusammen.
Eigenschaft | Wert in einer abgeleiteten Klasse | |
CanRead |
Gibt an, ob der aktuelle Stream Lesevorgänge unterstützt. |
R |
CanWrite |
Gibt an, ob der aktuelle Stream Schreibvorgänge unterstützt. |
R |
CanSeek |
Gibt an, ob der aktuelle Stream Suchvorgänge unterstützt. |
R |
CanTimeout |
Gibt an, ob eine Zeitüberschreitung berücksichtigt wird. |
R |
Length |
Die Länge des Streams in Byte |
R |
Position |
Die Position im aktuellen Stream |
|
ReadTimeout |
Die Zeit, nach der eine Leseoperation abgebrochen wird |
|
WriteTimeout |
Die Zeit, nach der eine Schreiboperation abgebrochen wird |
Methoden
Die wichtigsten Methoden aller Stream-Klassen sind Read, Write und Seek. Fangen wir mit Read und Write an, die von jeder abgeleiteten Klasse überschrieben werden müssen. Ein geöffneter Stream sollte am Ende ordnungsgemäß mit Close geschlossen werden.
Public MustOverride Function Read( _ buffer As Byte(), offset As Integer, count As Integer) As Integer Public MustOverride Sub Write( _ buffer As Byte(), offset As Integer, count As Integer) |
Die Write-Methode liest den ersten Parameter byteweise ein und schreibt die Daten in den Strom. Der Empfänger des Datenstroms nimmt die Bytes aus dem ersten Parameter von Read. Der zweite Parameter, offset, bestimmt die Position im Array, ab der der Lese- bzw. Schreibvorgang beginnt, meistens null. Der letzte Parameter gibt an, wie viele Bytes gelesen oder geschrieben werden sollen.
Im Rückgabewert von Read steht die Anzahl gelesener Bytes. Er ist null, wenn das Ende des Streams erreicht ist. Er kann kleiner sein als der dritte Parameter, wenn der Stream nicht genug Daten hat.
Die abstrakte Klasse Stream implementiert zwei Methoden, die nur ein Byte aus dem Datenstrom lesen oder in ihn hineinschreiben. ReadByte gibt -1 zurück, wenn das Ende des Datenstroms erreicht ist.
Public Overridable Function ReadByte() As Integer Public Overridable Sub WriteByte(ByVal value As Byte) |
Den Datenzeiger bewegt Seek. Der Parameter offset beschreibt die Verschiebung in Bytes ab der unter origin festgelegten Ursprungsposition.
Public MustOverride Function Seek(offset As Long, origin As SeekOrigin) _
As Long |
origin ist vom Typ der Enumeration SeekOrigin (siehe Tabelle 7.14).
Konstante | Beschreibung |
Begin |
Gibt den Anfang eines Streams an (das erste Byte). |
Current |
Gibt die aktuelle Position innerhalb eines Streams an. |
End |
Gibt das Ende eines Streams an (erste Position hinter dem letzten Byte). |
Tabelle 7.15 listet die Methoden von Stream auf.
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. |
7.4.2 Stream-Klassen im Überblick
Tabelle 7.16 zeigt einige der von Stream abgeleiteten Klassen aus verschiedenen Namensräumen.
Stream-Typ | Beschreibung | |
BufferedStream |
Durch Pufferung der Daten im Hauptspeicher wird die Zahl an Betriebssystemaufrufen reduziert und der Stream beschleunigt. |
|
CryptoStream |
Verschlüsselt gelesene oder geschriebene Daten. |
|
FileStream |
Zugriff auf das Dateisystem, lokal oder über das Netzwerk |
B |
GZipStream |
Dekompriemierung gelesener bzw. Komprimierung geschriebener Daten |
|
MemoryStream |
Sehr schneller Ersatz im Arbeitsspeicher für temporäre Dateien |
B |
NetworkStream |
Zugriff auf Sockets. Daten können nur vollständig gelesen/geschrieben werden; kein wahlfreier Zugriff. |
B |
7.4.3 Dateizugriff mit FileStream
Dieser Strom kann aus beliebigen Dateien byteweise lesen oder in sie schreiben. Ein Positionszeiger kann auf eine beliebige Position innerhalb des Streams gesetzt werden. Der Datenpuffer von standardmäßig 8 KByte erhöht die Ausführungsgeschwindigkeit.
Die FileStream-Klasse bietet eine Reihe von Konstruktoren an. In der folgenden Syntax sind optionale Parameter kursiv und Alternativen durch einen senkrechten Strich getrennt.
Public Sub New(path As String, mode As FileMode, _ access As FileAccess, _ share As FileShare, bufferSize As Integer, _ useAsync As Boolean | options As FileOptions) Public Sub New(path As String, mode As FileMode, _ rights As FileSystemRights, _ share As FileShare, bufferSize As Integer, options As FileOptions, _ fileSecurity As FileSecurity) Public Sub New(handle As SafeFileHandle, access As FileAccess, _ bufferSize As Integer, isAsync As Boolean) |
Der Parameter path gibt den Pfad zur Datei an, mode ist in Tabelle 7.3, »Die Enumeration FileMode«, (FileMode.Append, FileMode.Create, ...) beschrieben. Den Zugriff (FileAccess.Read, FileAccess.Write oder FileAccess.ReadWrite) spezifiziert access (siehe Tabelle 7.4, »Die Enumeration FileAccess«), während share konkurrierende Zugriffe regelt (siehe Tabelle 7.5, »Die Enumeration FileShare«). Im fünften Parameter geben Sie die Puffergröße an.
Mit dem Parameter useAsync kann noch angegeben werden, ob das Objekt asynchrone Zugriffe unterstützen soll.
Schreiben
Das folgende Codefragment demonstriert, wie mit einem FileStream-Objekt Daten in eine Datei geschrieben werden.
Sub Test() Dim arrWrite() As Byte = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100} Dim path As String = IO.Path.GetTempFileName() Dim fs As New FileStream(path, FileMode.Create) fs.Write(arrWrite, 0, arrWrite.Length) ' lesen, siehe unten fs.Close() File.Delete(path) Console.ReadLine() End Sub
Zunächst wird ein Byte-Array deklariert und mit insgesamt zehn Zahlen initialisiert. Die zweite Anweisung legt den Dateinamen fest, in den das Array geschrieben wird. Das FileStream-Objekts wird unter Angabe des Dateipfads und des Modus erzeugt. Die Konstante FileMode.Create erlaubt dem FileStream-Objekt, eine neue Datei zu erzeugen, oder, falls im angegebenen Pfad bereits eine gleichnamige Datei existiert, diese zu überschreiben. Mit
fs.Write(arrWrite, 0, arr.Length)
wird der Inhalt des Arrays arrWrite dem Stream-Objekt übergeben. Die Syntax lautet:
Public Overrides Sub Write( _
array As Byte(), offset As Integer, count As Integer) |
Dabei haben die drei Parameter die in Tabelle 7.17 gezeigte Bedeutung.
Parameter | Beschreibung |
array |
Ein Byte-Array, in das die übergebenen Daten gelesen werden |
offset |
Die Indexposition im Array, bei dem die Leseoperation beginnen soll |
count |
Die Anzahl der zu lesenden Bytes |
Der Schreibvorgang des Beispiels startet mit dem ersten Array-Element (zweiter Parameter) und schreibt das ganze Array (dritter Parameter). Zum Schluss wird der FileStream mit der Methode Close geschlossen.
Positionszeiger
Die Schreib- bzw. Leseposition in einem Datenstrom wird durch einen Positionszeiger beschrieben. Dadurch kennt der Strom das nächste zu bearbeitende Byte. Nach der Instanziierung steht der Zeiger auf dem ersten Byte. Mit jeder Schreib- und Leseoperation wird der Positionszeiger um die Anzahl verarbeiteter Bytes weitergeschoben. In unserem Beispiel werden 10 Bytes eingelesen, der Positionszeiger steht nach der Schreiboperation also auf dem elften Byte. Folgende Leseoperationen starten dort und haben nichts zu lesen, weil der Positionszeiger an der falschen Stelle steht. Abbildung 7.3 zeigt die Situation.
Abbildung 7.3 Positionszeiger eines Stroms
Vor der nächsten Leseoperation müssen wir den Positionszeiger mit der Methode Seek an eine passende Stelle setzen.
Public Overrides Function Seek(offset As Long, origin As SeekOrigin) As Long |
Um eine Datei komplett lesen zu können, setzen wir den Positionszeiger auf das erste Byte, origin + offset muss also null ergeben. Mit den in Tabelle 7.14, »Die Enumeration SeekOrigin«, aufgelisteten Werten haben Sie in unserem Beispiel drei gleichwertige Möglichkeiten, auf das erste Byte an der Position null zu springen.
fs.Seek(0, SeekOrigin.Begin) fs.Seek(-10, SeekOrigin.Current) fs.Seek(-10, SeekOrigin.End)
Welche Variante Sie wählen, ist Geschmackssache.
Lesen
Das Lesen aus einem FileStream können wir an der gerade geschriebenen Datei testen. Dazu ergänzen wir das Beispiel um ein paar Zeilen:
Sub Test() ... Dim arrRead(9) As Byte fs.Read(arrRead, 0, 10) For i As Integer = 0 To arrRead.Length – 1 Console.WriteLine(arrRead(i)) Next fs.Close() End Sub
Da wir die Anzahl zu lesender Bytes kennen, deklarieren wir ein Byte-Array passender Größe und lesen die Datei mit der Methode Read aus:
Public Overrides Function Read( _
array As Byte(), offset As Integer, count As Integer) As Integer |
Die auszulesende Datei wurde bereits bei der Erzeugung des FileStream-Objekts im letzten Abschnitt spezifiziert. Die Parameter sind analog zur Write-Methode. Die aus der Datei gelesenen Daten werden in den ersten Parameter, array, geschrieben, beginnend bei der im zweiten Parameter, offset, angegebenen Position. Die Anzahl zu lesender Bytes steht im dritten Parameter, count. Im Beispiel fangen wir durch 0 am Anfang des Arrays an und lesen 10 Bytes und nutzen so das gesamte Array. Zur Kontrolle geben wir den Arrayinhalt in einer Schleife aus.
Lesen Sie über das Ende der Datei hinaus, wird eine ArgumentException ausgelöst.
Hinweis |
Zur Konvertierung der Bytes in ein anderes Datenformat bietet die Klasse System.Convert einige Methoden an. |
Schreiben und Lesen
Zum Schluss wollen wir noch einmal den Code zusammenfassen:
'...\IO\Ströme\Dateistrom.vb |
Option Strict On Imports System.IO Namespace EA Module Dateistrom Sub Test() ' schreiben Dim arrWrite() As Byte = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100} Dim path As String = IO.Path.GetTempFileName() Dim fs As New FileStream(path, FileMode.Create) fs.Write(arrWrite, 0, arrWrite.Length) 'Positionszeiger setzen fs.Seek(0, SeekOrigin.Begin) ' lesen Dim arrRead(9) As Byte fs.Read(arrRead, 0, 10) For i As Integer = 0 To arrRead.Length – 1 Console.WriteLine(arrRead(i)) Next ' schließen fs.Close() File.Delete(path) Console.ReadLine() End Sub End Module End Namespace
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.