Start - Publikationen - Wissen - TOGAF - Impressum -

Arbeiten mit Streams


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.

  • Input/Output Bestimmt die Richtung in die der Stream Daten schieben soll. Ein Input-Stream hat typischerweise eine read-Methode, ein Output-Stream eine write-Methode. Die meisten Streams findet man paarweise in java.io, zu einem Input-Stream oder Reader gibt es den komplementären Output-Stream oder Writer.
  • Bytes/Characters Streams können unformatierte Bytes oder formatierte Character lesen. Typischerweise nennt man die byte-basierten Streams Input/Output-Streams und die character-basierten Streams Reader und Writer. Java nutzt den Unicode Standard für formatierte Streams.
  • Source/Sink/Filter Charakterisiert Streams nach Quelle/Ziel. Ist Quelle oder Ziel ein Datenspeicher (Filesystem, RAM, Pipe etc.), dann handelt es sich um Source/Sink. Ist Quelle oder Ziel ein anderer Stream, dann spricht man auch von einem Filter.
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
(1) Keine OutputStream/Writer-Implementierung vorhanden (2) Keine InputStream/Reader-Implementierung vorhanden (3) Nicht abstract deklariert, aber designed zur Vererbung (4) deprecated

Encoding


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:

  • InputStreamReader - wird benutzt wie ein formatierter Eingabestrom (Reader), nutzt selbst aber eine unformatierte Quelle
  • OutputStreamWriter - wird benutzt wie ein formatierter Ausgabestrom (Writer), nutzt selbst aber ein unformatiertes Ziel
Ein wichtiger Anwendungsfall für diese Vermittlerklassen ist das Lesen und Schreiben einer Datei mit einem bestimmten Encoding, denn die Klassen FileReader / FileWriter lassen keine explizite Angaben des Encodings zu und nehmen immer das Standard Encoding der Umgebung:
// 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


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();
copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved