Dateiformate

Beim Austausch von Dokumenten ist das Dateiformat entscheidend, damit die Darstellung und Wiedergabe optimal gelingt. Bei jedem Dokument – sei es binär oder textuell, eine Grafik oder eine Musikdatei – muss das Format der Zeichen oder Bytes definiert sein, damit das Einlesen und die Verarbeitung möglich sind. Die Definition vom Format ist je nachdem offen beschrieben (wie bei XML, PNG, OpenDocument), proprietär, aber dokumentiert (RTF, DOC) oder auch intern nur den Unternehmen bekannt (wie das Skype-Datenformat). Einige Textformate sind gleichzeitig Auszeichnungssprachen, denn wenn alles Text ist, muss irgendwie unterschieden werden, was Auszeichnungen sind und was die eigentliche Information ist. Bei XML zum Beispiel stehen die Auszeichnungen in spitzen Klammern. Die bekannteste Auszeichnungssprache ist sicherlich HTML; Akademikern war oder ist TeX vertraut. Mittlerweile sind viele moderne Auszeichnungssprachen XML-basiert. Dazu zählen DocBook für Dokumentationen, Atom für Web-Feeds, ebXML für elektronische Geschäftsprozesse, MathML für mathematische Formeln, OpenDocument und Office Open XML (die jedoch in einem Zip-Archiv standardmäßig komprimiert werden.) Oft gehen Betriebssysteme über die Dateiendung, um den Typ bzw. das Format einer Datei herauszulesen, oder nutzen spezielle Metadaten bzw. interpretieren die ersten Bytes, um das Format zu erkennen. Wenn Dokumente etwa in einer Datenbank abgelegt oder vom Webserver verschickt werden, haben sie keinen Dateinamen, und so werden zusätzliche Metadaten gespeichert. Im Internet beschreibt der MIME-Typ die Mediendaten. NIO.2 kann versuchen, den MIME-Type zu ermitteln. Da die Grundlage jedes Dokuments ein Byte ist, lässt sich natürlich immer ein Bytestrom öffnen und lassen sich die Daten selbst verarbeiten. Wir suchen jedoch eine andere, höhere Abstraktion. Die fängt schon in Java selbst damit an, dass Textdokumente nicht mit InputStream/OutputStream verarbeitet und die Strings dann von Hand in Unicode konvertiert werden, sondern dass ein Reader/Writer die Arbeit verrichtet. Wenn wir es mit XML-Dateien zu tun haben, so holen wir uns auch keinen Reader zum Lesen, sondern greifen auf elegante XML-Klassen zurück, die uns die Arbeit beim Lesen der Elemente abnehmen. Dieses Kapitel stellt unterschiedliche Aspekte mit Dateiformaten und Java heraus. Zum einen werden unterschiedliche Dateien und Dokumentformate vorgestellt, und zum anderen werden auch Aspekte wie Darstellung, Wiedergabe und Konvertierung angesprochen.

Einfache Dateiformate für strukturierte Daten

Daten sind in der Regel immer strukturiert. Diese Strukturierung bekommen die Daten durch eingebettete besondere Symbole oder Symbolfolgen. Das kann etwa ein Zeilenumbruchzeichen sein oder ein Token-Separator wie »=« oder XML für hierarchische Dokumente.

Property-Dateien

Dateien, die Schlüssel-Werte-Paare als Sting repräsentieren und die Schlüssel und Wert durch ein Gleichheitszeichen trennen, nennen sich Property-Dateien. Sie kommen zur Programmkonfiguration häufig vor, und Java bietet mit der Klasse Properties die Möglichkeit, die Property-Dateien einzulesen und zu schreiben. In Kapitel 3, »Datenstrukturen und Algorithmen«, kam die Klasse mit Beispielen schon zur Sprache.

CSV-Dateien

Eine CSV-(Comma-separated-Values-)Datei bildet die Zeilen und Spalten einer Tabelle in einer ASCII-Datei ab. Die Zellen sind dabei durch ein Komma oder ein anderes Trennzeichen separiert. Texte können in Anführungszeichen gesetzt werden, um etwa Leerzeichen zu berücksichtigen.
Rodney,King,"Fahrer"
Bryant,Allen,"Gast auf dem Rücksitz"
Auch MS Excel kann Tabellen in das CSV-Format exportieren, nutzt aber in der deutschen Version als Trenner ein Semikolon – CSV wird bei MS also zu einer sprachabhängigen Datei (außer der Export wird über ein englischsprachiges Makro angestoßen, da ist es wieder ein Komma). In der ersten Zeile stehen die Tabellenköpfe. Sollten CSV-Dateien in Java verarbeitet werden, fällt spontan die Klasse StringTokenizer auf, die zum Einlesen jedoch nicht besonders gut geeignet ist. Welches Trennsymbol sollte gewählt werden? Sicherlich das Semikolon. Doch was passiert, wenn dieses im Text vorkommt? Dann wird der Text in zwei Tokens aufgeteilt – was falsch ist. Des Weiteren kann der Java-StringTokenizer nicht mit Leerstrings umgehen, also auf Zeilenfolgen wie ;; im Datenstrom reagieren; er würde sie überlesen, aber nicht einen leeren String zurückgeben. StringTokenizer hilft hier nicht weiter, wohl aber eine quelloffene Bibliothek von Stephen Ostermiller unter http://ostermiller.org/utils/CSV.html. Mit dem CSVParser lassen sich leicht CSV-Dateien einlesen, und er behandelt auch Fluchtsymbole korrekt.
CSVParser csvParser
new CSVParser( new FileInputStream("datei.csv") );
for ( String t; (t  csvParser.nextValue()) != null; )
System.out.println( csvParser.lastLineNumber() + " " + t );

JSON-Serialisierung mit Jackson

Im Internet hat ein JSON das XML-Format zwecks Objektübertragung zwischen Server und Browser fast vollständig verdrängt. Das liegt daran, dass ein Browser JSON-Strings direkt in JavaScript-Objekte konvertieren kann, XML-Dokumente aber erst aufwändiger verarbeitet werden müssen. Neben dem Einsatzgebiet im Internet bietet JSON auch ein kompaktes Format, um etwa lokale Konfigurationsdateien zu kodieren. Nehmen wir folgende Zeile JavaScript-Code, die ein Person-Objekt mit zwei Properties für Name und Alter definiert. Eine Property wird über ein Schlüssel-Werte-Paar beschrieben:
var person = { "name" : "Michael Jackson", "age" : 50 };
Die Definition eines Objekts geschieht in der JSON (JavaScript Object Notation). Als Datentypen unterstützt JSON Zahlen, Wahrheitswerte, Strings, Arrays, null und Objekte – wie unser Beispiel zeigt. Die Deklarationen können geschachtelt sein, um Unterobjekte aufzubauen. Zum Zugriff auf die JSON-Daten kommt der Punkt zum Einsatz, sodass der Name nach der Auswertung durch person.name zugänglich ist. Eine Personenbeschreibung wie diese kann auch in einem String stehen, der von JavaScript zur Laufzeit ausgewertet wird.
var json ='person = { "name" : "Michael Jackson", "age" : 50 };';
eval( json );
Der Zugriff auf person.name liefert wie vorher den Namen, denn nach der Auswertung mit eval() wird JavaScript ein neues Objekt mit person im Kontext anlegen. JSON ist besonders praktisch, wenn es darum geht, Daten zwischen einem Server und Browser mit JavaScript-Interpreter auszutauschen. Denn wenn der String json nicht von Hand mit einem String initialisiert wurde, sondern ein Server die Zeichenkette person = { ... }; liefert, haben wir das, was heutzutage in modernen Ajax-Webanwendungen passiert. Die letzte Frage ist nun, wie elegant der Server Zeichenketten im Datenaustauschformat JSON erzeugt und so Objekte überträgt. Den String per Hand aufzubauen ist eine Lösung, aber es geht besser. Die Open-Source-Bibliothek Jackson (http://jackson.codehaus.org) gehört zu den populärsten Lösungen. Sie liest JSON-Daten ein, gibt sie aus und überträgt sie auf JavaBeans, sodass eine unkomplizierte Serialisierung in JSON möglich wird:
ObjectMapper mapper = new ObjectMapper();
MyClass myObject = mapper.readValue( input, MyClass.class );
mapper.writeValue( output, myObject );
Der ObjectMapper übernimmt das Lesen/Schreiben. In der zweiten Zeile wird aus der Eingabequelle input gelesen und ein Objekt vom Typ MyClass rekonstruiert. In der dritten Zeile wird es in die Ausgabe output geschrieben.