Bei der Arbeit mit Streams und dem java.io Paket hilft mir immer wieder eine Kategorisierung, die ich in "Java Pitfalls" von Daconta, Monk, Keller, Bohnenberger gefunden habe.
Byte | Character | typische Verwendung |
---|---|---|
Source/Sink | ||
Input[Output]Stream | Reader[Writer] | abstract |
ByteArrayInput[Output]Stream | CharArrayReader[Writer] | RAM, Array basiert |
StringBufferInputStream (1,4) | StringReader[Writer] | RAM, String basiert |
PipedInput[Output]Stream | PipedReader[Writer] | Pipe, Datenströme zwischen Threads |
FileInput[Output]Stream | FileReader[Writer] | File basiert |
Filter | ||
FilterInput[Output]Stream (3) | FilterReader[Writer] | abstract |
BufferedInput[Output]Stream | BufferedReader[Writer] | zum Zwischenschalten eines RAM basierten Puffers |
PushbackInputStream (1) | PushbackReader (1) | Zum wiederholten Parsen des Streams |
SequenceInputStream (1) | Serielle Abarbeitung mehrerer InputStreams | |
LineNumberInputStream (1,4) | LineNumberReader (1) | Merkt sich beim Lesen die aktuelle Zeilennummer |
DataInput[Output]Stream | Lesen und schreiben primitiver Datentypen und Strings | |
CheckedInput[Output]Stream | CRC32 Berechnung beim Lesen | |
DigestInput[Output]Stream | java.security | |
InflaterInput[Output]Stream | java.util.zip, Kompression/Dekompression | |
ProgressMonitorInputStream (1) | öffnet beim Lesen eine Dialogbox mit Progressbar | |
PrintStream (2) | PrintWriter (2) | High Level Ausgaben und Unterdrückung von Exceptions |
ObjectInput[Output]Stream | Serialisierung/Deserialisierung von Objektgraphen |
Hardwarenahe Ressourcen sind praktisch immer byte-basiert. Damit lassen sich 256 Zeichen codieren, ein Zeichenvorrat der viel zu klein ist, um einen für Menschen interessanten Basissatz an Symbolen adressieren zu können. Deshalb werden für moderne Datenverarbeitung nicht Bytes, sondern Bytefolgen als Symbole interpretiert. Das Übersetzen von Symbolen in Bytefolgen und zurück wird encodieren/decodieren genannt, der Algorithmus "Encoding". Prominente Beispiele für ein solches Encoding sind UTF-8 oder Unicode.
Übrigens: die formatierten Streams arbeiten wie die Stringklasse Unicode-basiert. Diese Wahl ist plausibel, da im Unicode jedem Symbol zwei Bytes zugeordnet sind. Random Access ist auf diese Weise am schnellsten und zuverlässigsten zu realisierbar.
Für formatierte Streams wird das Encoding implizit gewählt:
// als Encoding wird implizit die System-Property <tt>file.encoding</tt> genutzt FileReader fr = new FileReader(file);Im allgemeinen ist das implizite Encoding die beste Wahl. Wenn das implizite Encoding nicht plausibel erscheint muss man die Stream-API konsultieren.
Natürlich kann das Encoding auch explizit angegeben werden Dazu bedient man sich der Klassen InputStreamReader und OutputStreamWriter. Diese spielen als Filter die Rolle des Vermittlers zwischen formatierten und unformatierten Streams und nehmen eine Sonderrolle ein. In beiden Fällen muss ein Encoding für die Konvertierung spezifiziert werden:
// Schreiben von Daten mit vorgegbenen Encoding FileOutputStream fos = null; OutputStreamWriter osw = null; try { fos = new FileOutputStream(new File("uft8data")); osw = new OutputStreamWriter(fos, "UTF-8"); osw.write("ÄÖÜ"); osw.flush(); } finally { if (osw != null) { osw.close(); } } ...
Filter Chaining ist eine Technik, bei der man mehrere Filter verkettet, wobei jeder Filter eine bestimmte Aufgabe übernimmt. Diese Strategie begegnet einem überall. Hier ein Beispiel, bei dem von der Console (eine Source) formatiert gelesen wird:
BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(System.in)); String result = br.readLine(); create = result.toUpperCase().startsWith("Y"); .. } finally { if (br != null) { br.close(); } }Das close wird in einer solchen Verkettung weitergereicht bis zur Quelle. Bei einem komplexeres Beispiel werden ein FileOutputStream, ZipOutputStream und ein ObjectOutputStream verkettet (und das ganze dann wieder zurück):
// schreibe Serialisierte Daten als Zip in ein File FileOutputStream fos = new FileOutputStream("objects.zip"); ZipOutputStream zos = new ZipOutputStream(fos); zos.putNextEntry(new ZipEntry("Shorts")); ObjectOutputStream oos = new ObjectOutputStream(zos); oos.writeObject(new short[]{ 9, 8, 7, 6, 5, 4, 3, 2, 1 }); zos.closeEntry(); zos.putNextEntry(new ZipEntry("Strings")); oos.writeObject(new String[]{ "9", "8", "7", "6", "5", "4", "3", "2", "1" }); zos.closeEntry(); oos.close();
// lese diese Daten FileInputStream fis = new FileInputStream("objects.zip"); ZipInputStream zis = new ZipInputStream(fis); zis.getNextEntry(); ObjectInputStream ois = new ObjectInputStream(zis); short[] array = (short[]) ois.readObject(); int i = 0; for (int j = 0; j < array.length; j++) { System.out.println(++i + ") " + array[j]); } zis.getNextEntry(); String[] array2 = (String[]) ois.readObject(); i = 0; for (int j = 0; j < array2.length; j++) { System.out.println(++i + ") " + array2[j]); } ois.close();