7.6 Binärdaten mit BinaryReader und BinaryWriter 

Bis jetzt haben wir von den primitiven Datentypen Bytes sowie Zeichen und Zeichenketten mit Strömen verarbeitet. Nun kommen wir zu den anderen Primitiven.
7.6.1 Schreiben mit BinaryWriter 

Ein BinaryWriter-Objekt erzeugen Sie unter Angabe des zu schreibenden Stroms. Optional können Sie die Kodierung von zeichenbasierten Daten angeben:
Public Sub New(output As Stream) |
Neben den Schreibroutinen gibt es nur Methoden zum Schließen, Pufferleeren und Setzen des Datenzeigers:
Public Overridable Sub Close() |
Den Kern bilden die Methoden zum Schreiben verschiedener primitiver Datentypen. Die Typen werden binär übermittelt, zum Beispiel 4 Bytes für Integer und 8 Bytes für Double:
Public Overridable Sub Write(value As Datum) |
Von Bytes und Zeichen können ganze Arrays gleichzeitig geschrieben werden:
Public Overridable Sub Write(buffer As Byte()) |
7.6.2 Lesen mit BinaryReader 

Ein BinaryReader-Objekt erzeugen Sie unter Angabe des zu lesenden Stroms. Optional können Sie die Kodierung von zeichenbasierten Daten angeben.
Public Sub New(input As Stream) |
Neben den Leseroutinen gibt es nur Methoden zum Schließen und Lesen ohne Fortschritt des Datenzeigers:
Public Overridable Sub Close() |
Den Kern bilden die Methoden zum Lesen verschiedener primitiver Datentypen. Es werden so viele Bytes gelesen, wie der Datentyp beansprucht, zum Beispiel 4 für Integer und 8 für Double. Die spezifischen Varianten lösen EndOfStreamException aus, wenn es nichts mehr zu lesen gibt.
Public Overridable Function Read() As Integer |
Von Bytes und Zeichen können auch mehrere gleichzeitig eingelesen werden:
Public Overridable Function Read( _ |
Auf den zugrunde liegenden Strom greifen Sie mit der Eigenschaft BaseStream zu.
Public Overridable ReadOnly Property BaseStream As Stream |
7.6.3 Binäre Datenströme auswerten 

Der Vorteil binärer Daten ist deren Effizienz bezüglich Speicherplatz und Geschwindigkeit. Ihr Nachteil ist der komplette Datenverlust, wenn auch nur ein Byte »falsch« interpretiert wird. Im folgenden Beispiel wird die Zahl 500 binär geschrieben und dann byteweise und als Text eingelesen.
'...\IO\Ströme\Binärdaten.vb |
Option Strict On
Imports System.IO
Namespace EA
Module Binärdaten
Sub Test()
Dim path As String = IO.Path.GetTempFileName()
Dim fi As New FileInfo(path)
' Datei erzeugen und mit Daten füllen
Dim fs As New FileStream(path, FileMode.Create)
Dim bw As New BinaryWriter(fs)
bw.Write(500)
bw.Close()
' Datei binär auswerten
Dim byteDaten(CType(fi.Length, Integer)) As Byte
fs = New FileStream(path, FileMode.Open)
fs.Read(byteDaten, 0, CType(fi.Length, Integer))
Console.Write("Als Byte-Array: ")
For Each b As Byte In byteDaten : Console.Write(b & " ") : Next
Console.WriteLine()
fs.Close()
' Datei als Text auswerten
Console.Write("Als Text: ")
Console.WriteLine(File.ReadAllText(path))
File.Delete(path)
Console.ReadLine()
End Sub
End Module
End Namespace
Das Lesen Byte für Byte mit der Methode Read von FileStream läuft völlig korrekt ab.
Als Byte-Array: 244 1 0 0 0
Wenn man das Originaldatenformat nicht kennt, gibt es keine Möglichkeit, um festzustellen, ob die Daten vier einzelne Bytes, zwei Short-Werten oder einen Integer repräsentieren (oder noch etwas ganz anderes). Zum Beispiel ist die Interpretation als Text nicht besonders überzeugend:
Als Text: ?
Schauen wir uns die Abbildung der vier Zahlen auf einen Integer an. Als Erstes müssen wir die richtige Reihenfolge berücksichtigen. Auf Intel-Prozessoren wird das niederwertigste Byte zuerst gespeichert (Little-Endian). Von groß nach klein sortiert ist die Bytefolge:
0 0 1 244
Das können wir direkt auf eine Bitfolge abbilden:
0000 0000 0000 0000 0000 0001 1111 0100
Die Kombination aller Bits ergibt tatsächlich die Dezimalzahl 500. Diese hardwareabhängige Konvertierung überlassen wir besser .NET und nutzen eine »passende« Methode:
Dim byteDaten(CType(fi.Length, Integer)) As Byte
Dim br as New BinaryReader(fs)
Console.WriteLine(br.ReadInt32())
7.6.4 Komplexe binäre Daten 

Auch nicht-primitive Datentypen können binär geschrieben und gelesen werden, denn letztendlich setzen sie sich aus primitiven Typen zusammen, die in die richtige Struktur (Klasse/Objekt) eingebettet sind. Zur korrekten Handhabung komplexer Typen als Binärdaten müssen bekannt sein:
- das Binärformat jedes primitiven Datentyps
- die Reihenfolge der Daten im Strom
- die Position jedes Datums im komplexen Typ als Funktion seiner Position im Strom
Da die beiden letzten Punkte für praktisch jede Anwendung verschieden sind, hat jedes Programm sein eigenes Binärdatenformat. Die Vorgehensweise bei der Behandlung solcher binärer Daten schauen wir uns am Beispiel einer einfachen Struktur an. Sie hat gegenüber einer Klasse den Vorteil, ein Werttyp zu sein, sodass wir nicht auch noch Referenzen auflösen müssen. So bleibt das Beispiel übersichtlicher, ohne seine Allgemeingültigkeit zu verlieren, denn die gezeigten Schritte lassen sich einfach auf beliebig komplexe andere Datentypen übertragen. Der Anschaulichkeit halber verwenden wir die Repräsentierung eines Punkts:
'...\IO\Ströme\Binärstruktur.vb |
Structure Punkt
Public X, Y As Integer
Public Farbe As Long
End Structure
Die ganzzahligen Datentypen sind bewusst verschieden gewählt, damit eine Behandlung als Integer-Array ausscheidet. Wir entwickeln eine Anwendung, die solche Punkte speichern kann und wahlfrei einen beliebigen Punkt auslesen kann. In der Datei stehen die Punkte einfach hintereinander. Die Information über deren Anzahl können wir auf zwei Arten erhalten:
- Das Ende der Datei wird durch Daten markiert, die unmöglich ein Punkt sein können.
- Am Anfang der Datei steht die Punktzahl.
Wir entscheiden uns für die zweite Variante (siehe Abbildung 7.4). Da alle Daten binär sind, muss auch das Format dieser Anzahl genau bekannt sein, damit jedes Byte in der Datei eine klare Zuordnung erhält. Im Folgenden wird diese Information in einem Integer gespeichert.
Abbildung 7.4 Datei mit drei gespeicherten Punkten
Die Auswertung der ersten vier Bytes liefert die Anzahl der gespeicherten Punkte, die folgenden insgesamt 16 Byte großen Blöcke beschreiben jeweils einen Punkt. Wir fassen die Methoden zur Handhabung in einem Modul namens Binärpunkte zusammen.
- Speichern
- Lesen
Die Methode Speichern bekommt als Parameter den Pfad zu einer Datei und ein Array mit den zu speichernden Punkten. Ein FileStream für diesen Pfad bildet die Basis eines BinaryWriter. Er schreibt zuerst die Anzahl Punkte binär in die Datei und dann in einer Schleife alle Punkte. Sie legt die Reihenfolge der Daten fest. Der Wert FileMode.Create für den Dateistrom garantiert, dass nur die in diesem Aufruf geschriebenen Daten in der Datei sind.
'...\IO\Ströme\Binärstruktur.vb |
Sub Schreiben(ByVal pfad As String, ByVal punkte As Punkt())
Dim fs As New FileStream(pfad, FileMode.Create)
Dim bw As New BinaryWriter(fs)
' Anzahl der Punkte in die Datei schreiben
bw.Write(punkte.Length)
' die Punkte in die Datei schreiben
For Each p As Punkt In punkte
bw.Write(p.X) : bw.Write(p.Y) : bw.Write(p.Farbe)
Next
bw.Close()
End Sub
Mit der Schreibroutine liegt das Datenformat fest, und wir können die Leseroutine implementieren. Das Lesen erfolgt in derselben Reihenfolge wie das Schreiben:
'...\IO\Ströme\Binärstruktur.vb |
Function Lesen(ByVal pfad As String) As Punkt()
Dim fs As New FileStream(pfad, FileMode.Open)
Dim br As New BinaryReader(fs)
' die ersten 4 Bytes spezifizieren die Anzahl der Punkte
Dim anzahl As Integer = br.ReadInt32()
' einlesen der Punkte
Dim punkte(anzahl – 1) As Punkt
For i As Integer = 0 To anzahl – 1
punkte(i).X = br.ReadInt32() : punkte(i).Y = br.ReadInt32()
punkte(i).Farbe = br.ReadInt64()
Next
br.Close()
Return punkte
End Function
Da uns der Platzbedarf aller Daten bekannt ist, können wir an die richtige Position in der Datei springen und einen einzelnen Punkt einlesen. Um das Beispiel einfach zu halten, verzichte ich auf die Implementierung eigener Ausnahmeklassen.
'...\IO\Ströme\Binärstruktur.vb |
Function Lesen(ByVal pfad As String, ByVal nr As Integer) As Punkt
Dim fs As New FileStream(pfad, FileMode.Open)
Dim pos As Integer = 4 + (nr – 1) * 16
Dim br As New BinaryReader(fs)
' Ist die Position gültig?
If nr <= 0 Then Throw New ArgumentException("Punkt null gibt's nicht")
If nr > br.ReadInt32() Then Throw New ArgumentException("Zu groß")
' Daten des gewünschten Points einlesen
fs.Seek(pos, SeekOrigin.Begin)
Dim p As New Punkt()
p.X = br.ReadInt32() : p.Y = br.ReadInt32():p.Farbe = br.ReadInt64()
br.Close()
Return p
End Function
Am Dateianfang belegt die Punktzahl 4 Bytes, danach belegt jeder Punkt 16 Bytes. Damit haben wir die Position vor dem einzulesenden Punkt:
Dim pos As Integer = 4 + (nr – 1) * 16
Sie wird der Seek-Methode des BinaryReader übergeben:
fs.Seek(pos, SeekOrigin.Begin)
Damit ist unsere Klassendefinition fertig, und wir können abschließend die Implementierung testen. Dazu erzeugen wir ein Array mit zwei Testpunkten und speichern es mit Binärpunkte.Schreiben in eine Datei. Diese lesen wir mit Binärpunkte.Lesen aus und geben die gelesenen Punkte aus. Schließlich lesen wir einen Einzelpunkt und geben ihn aus.
'...\IO\Ströme\Binärstruktur.vb |
Option Strict On
Imports System.IO
Namespace EA
Structure Punkt ...
Module Binärpunkte ...
Module Binärstruktur
Sub Test()
Dim path As String = IO.Path.GetTempFileName()
' Testpunkte erzeugen
Dim punkte(1) As Punkt
punkte(0).X = 10 : punkte(0).Y = 20 : punkte(0).Farbe = 310
punkte(1).X = 40 : punkte(1).Y = 50 : punkte(1).Farbe = 110
' Punkte speichern
Binärpunkte.Schreiben(path, punkte)
' Punkte einlesen und ausgeben
For Each p As Punkt In Binärpunkte.Lesen(path)
Console.WriteLine("{{X={0}, Y={1}, Farbe={2}}}", p.X, p.Y, p.Farbe)
Next
' Einzelpunkt einlesen und ausgeben
Console.Write("Nummer des einzulesenden Punkts: ")
Dim nr As Integer = Convert.ToInt32(Console.ReadLine())
Try
Dim p As Punkt = Binärpunkte.Lesen(path, nr)
Console.WriteLine("{{X={0}, Y={1}, Farbe={2}}}", p.X, p.Y, p.Farbe)
Catch ex As ArgumentException
Console.WriteLine("Fehler: {0}", ex.Message)
End Try
File.Delete(path)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe bestätigt die korrekte Implementierung:
{X=10, Y=20, Farbe=310}
{X=40, Y=50, Farbe=110}
Nummer des einzulesenden Punkts: 1
{X=10, Y=20, Farbe=310}
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.