09.05 Beliebige Daten lesen und schreiben
Im letzten Kapitel haben Sie gelernt, wie Sie Text in einer Textdatei speichern können. Aber natürlich gibt es noch viel mehr Arten von Daten als Texte. Bilder und Musik liegen bspw. nicht als geschriebener Text, sondern in binärer Form vor. In diesem Fall greifen Sie auf java.io.FileInputStream
anstelle von java.io.FileReader
und java.io.FileOutputStream
anstelle von java.io.FileWriter
zu. Der Hauptunterschied liegt darin, dass Bytes anstelle von Zeichen verarbeitet werden. Die Handhabung ist hingegen weitestgehend identisch – Sie können sogar mittels java.io.BufferedInputStream
und java.io.BufferedOutputStream
die Streams wie im vorhergehenden Kapitel puffern.
Text „binär“ speichern
Auch Text besteht in der Computerwelt genau genommen aus Nullen und Einsen und somit Bits und Bytes. Deshalb können Sie mit Streams genauso Text schreiben und Lesen wie mit Readern und Writern. Sie müssen die Buchstaben lediglich in Bytes konvertieren. Sehen Sie sich hierzu ein einfaches Einstiegsbeispiel an:
File f = new File("C:/test.txt"); FileOutputStream fos = null; try { fos = new FileOutputStream(f); fos.write("Test Ausgabe".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) try { fos.close(); } catch (IOException e) {} } FileInputStream fis = null; try { fis = new FileInputStream(f); byte[] b = new byte[(int)f.length()]; fis.read(b); System.out.println(new String(b)); } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) try { fis.close(); } catch (IOException e) {} }
Sie sehen, dass sich der Einsatz von Streams nur sehr geringfügig vom bereits Gelerntem unterscheidet. Aber das ist natürlich nicht der häufigste Einsatzort für Streams.
Daten binär speichern
Die bekanntesten binär verfügbaren Dateien sind wohl Musikstücke (z. B. im Dateiformat mp3), Bilder (bspw. jpeg, png, bmp, tiff), PDF-Dateien für den Adobe Acrobat Reader und Programme (exe (Windows)). Der Inhalt dieser Dateien liegt in Bits und Bytes vor und kann von entsprechenden Programmen (Bildbearbeitungssoftware, Musikplayer, Acrobat Reader) bzw. direkt vom Betriebssystem interpretiert und wiedergegeben werden. Dazu muss die Datei aber genau so aufgebaut werden, wie es die verarbeitende Schnittstelle erwartet. Dieser Aufbau stellt das so genannte (Datei-) Format der Daten dar. Dieses spezifiziert, welche Informationen an welcher Stelle stehen müssen. So kann es z. B. vorgegeben sein, dass an den Anfang der Datei ein Header mit Metainformationen gestellt wird. Erst nach diesem Header folgen die Daten in einer ganz bestimmten Reihenfolge und Form.
Ein solches gängiges Dateiformat an dieser Stelle zu lesen und schreiben würde den Rahmen sprengen, zumal es für viele Formate bereits eine Standardimplementierung in Java gibt. Deshalb beschränken wir uns in diesem Kapitel auf eine einfachere Anforderung: Einen großen Long
-Wert jenseits der Integer
-Grenze möglichst speichersparend in eine Datei zu schreiben.
Angenommen der größtmögliche Long
-Wert würde als String
in eine Datei geschrieben werden:
FileOutputStream fos = null; try { fos = new FileOutputStream("C:/test.txt"); fos.write(String.valueOf(Long.MAX_VALUE).getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) try { fos.close(); } catch (IOException e) {} }
Als Resultat bekommen Sie eine Textdatei, die 19 Byte groß ist, und folgendes beinhaltet:
9223372036854775807
Falls Sie sich noch an das Kapitel 02.03 Primitive Datentypen erinnern können, ist ein long
aber nur acht byte groß – und keine 19. Dies liegt daran, dass bei der soeben gezeigten Variante für jede einzelne Ziffer ein separates Byte in die Datei geschrieben werden muss. In Wirklichkeit liegen die Ziffern aber in binärer Form direkt als eine große Zahl in mehreren aufeinanderfolgenden Bytes vor. Wenn Sie den Long
mit Hilfe einer einfachen Konvertierungsmethode vor dem Speichervorgang in ein entsprechendes byte
-Array umwandeln, und dieses dann abspeichern, können Sie den Platzbedarf der Datei auf der Festplatte auf acht Byte reduzieren.
FileOutputStream fos = null; try { fos = new FileOutputStream("C:/test.txt"); fos.write(convert(Long.MAX_VALUE)); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) try { fos.close(); } catch (IOException e) {} } ... public byte[] convert(long l) { byte[] b = new byte[8]; for (int i = 0; i < b.length; i++) { b[i] = (byte)((l & (255L << (56 - 8 * i))) >> (56 - 8 * i)); } return b; }
Allerdings sollten Sie nun nicht mehr erwarten, dass die Textdatei beim Öffnen mit einem gewöhnlichen Editor noch Rückschlüsse auf den eigentlichen Inhalt oder gar Manipulationsmöglichkeiten zulässt. Der Inhalt wird vermutlich so oder so ähnlich aussehen:
ÿÿÿÿÿÿÿ
Dennoch können Sie – indem Sie die Konvertierung nach dem Lesen der Datei umkehren – die gespeicherte Zahl wieder in Ihrem Java-Programm auslesen und verwenden:
FileInputStream fis = null; try { fis = new FileInputStream("C:/test.txt"); byte[] b = new byte[8]; fis.read(b); System.out.println(convert(b)); } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) try { fis.close(); } catch (IOException e) {} } ... public long convert(byte[] bytes) { long l = 0; for (int i = 0; i < bytes.length; i++) { l |= (long)((bytes[i] & 0xFFL) << (56 - 8 * i)); } return l; }
Dies kann auch jedes andere Programm. Jedoch nur, wenn es die Konvertierung (das Dateiformat) kennt.
Basistypen schreiben
Selbstverständlich müssen Sie sich in Java nicht so wie eben verbiegen, nur um effizient einen Datentypen wie den long
zu schreiben. Java bietet Ihnen hier den java.io.DataOutputStream
und den java.io.DataInputStream
. Mit diesen Streams können Sie häufig verwendete Datendarstellungen lesen und schreiben. Bspw. schreibt/liest write/readLong
einen long
, write/readByte
ein byte
und read/writeUTF
einen String
in einem modifiziertem UTF-8 Format.
Um einen DataInput/DataOutputStream
zu erzeugen, übergeben Sie diesem im Konstruktor einfach den gewünschten OutputStream
oder InputStream
. Als Beispiel schreiben wir N Strings
mit vorangestellter Anzahl der Strings
in eine Datei und lesen diese später wieder aus.
String[] strings = { "ich bin ein String-Array", "und bestehe aus mehreren Strings", "die alle in eine Datei geschrieben werden" }; DataOutputStream dos = null; FileOutputStream fos = null; try { fos = new FileOutputStream("strings.bin"); dos = new DataOutputStream(fos); dos.writeInt(strings.length); for (String str : strings) { dos.writeUTF(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (dos != null) try { dos.close(); } catch (IOException e) {} if (fos != null) try { fos.close(); } catch (IOException e) {} }
DataInputStream dis = null; FileInputStream fis = null; try { fis = new FileInputStream("strings.bin"); dis = new DataInputStream(fis); String[] strs = new String[dis.readInt()]; for (int i = 0; i < strs.length; i++) { strs[i] = dis.readUTF(); System.out.println(strs[i]); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (dis != null) try { dis.close(); } catch (IOException e) {} if (fis != null) try { fis.close(); } catch (IOException e) {} }
Super Sache, genau danach hatte ich gerade gesucht..!