Was ist neu in Java 7? Teil 2 – Performance

Seite 2: I/O-APIs der Java-Plattform und Fazit

Inhaltsverzeichnis

Das Thema I/O nimmt im neuen Java-7-Release eine besondere Stellung ein. Mit den "More New I/O APIs for the Java Platform" (NIO.2) kommt die 2006 begonnene Arbeit unter dem Dach des JSR 203 zu einem Abschluss. Auch an der Stelle ein wenig Geschichte: Bereits 2002 führte der JSR 51 ("New I/O APIs for the Java Platform") Buffer und Channel sowie nicht blockierende Sockets und Zeichensätze ein. Die unter java.io.File gesammelten Funktionen waren aber verbesserungswürdig und neben vielen störenden Kleinigkeiten – vor allem die fehlenden Basisfunktionen (Kopieren, Verschieben etc.) – ein Ärgernis. NIO.2 vereinheitlicht jetzt einiges, die nennenswerten neuen Klassen und Interfaces liegen dabei im package java.nio.file.*.

Die neue Schnittstelle Path identifiziert eine Datei im Dateisystem. Ein Path lässt sich auf diverse Arten erzeugen. Ergebnis ist dabei immer eine plattformspezifische Implementierung (beispielsweise für Windows: sun.nio.fs.WindowsPath). Dabei sind Methoden zum Zugriff, Vergleich und Verändern von Pfaden vorhanden.

// Path von Subpaths via FileSystem
Path path = FileSystems.getDefault().getPath("d:", "temp","test.txt");
// Path via Paths
Path path = java.nio.file.Paths.get("d:", " temp "","test.txt");
// Path via File
File file = new java.io.File("D:/temp"","test.txt");
Path path = file.toPath();

Die neue Klasse Files kann direkt auf den Path-Objekten arbeiten. Hier sind jetzt alle Basisbefehle vorhanden: neben dem Erstellen und Öffnen (zum Schreiben bzw. Lesen) auch die Genannten zum Kopieren, Verschieben und Löschen. Mit dem Interface FileAttribute lassen sich auch entsprechende Attribute hinterlegen. Die plattformspezifischen Implementierungen liegen unter java.nio.file.attribute.*. Sie stellen eine Sicht auf die beim jeweiligen Dateisystem vorhandenen Attribute dar.

DosFileAttributes attrs = Files.readAttributes(path, DosFileAttributes.class);
FileTime time = attrs.creationTime();
long size = attrs.size();

Die Files-Klasse unterstützt jetzt rekursive Operationen auf dem Dateisystem. Ein einfaches Beispiel ist mit dem SimpleFileVisitor schon vorhanden. Alle Methoden für den Zugriff auf das Dateisystem werfen nun eine einheitliche IOException. Spezifische Exceptions werden nur noch für einzelne, wiederherstellbare Fehlersituationen geworfen. Fast schon obligatorisch ist die nun vorhandene Unterstützung für symbolische Links.

Mit dem Interface DirectoryStream lässt sich auf Verzeichnisinhalten arbeiten. Dazu gehört auch ein auf regulären Ausdrücken basierter Filter. Der folgende Code listet alle *.html- und *.txt-Dateien im Verzeichnis:

List<Path> listSourceFiles(Path dir) throws IOException {
List<Path> result = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.{html,txt}")) {
for (Path entry : stream) {
result.add(entry);
}
} catch (DirectoryIteratorException ex) {
throw ex.getCause();
}
return result;
}

File-Systeme kann lassen sich über das neue Interface FileSystem abstrahieren. Mit den Service-Provider-Interfaces (FileSystemProvider) lassen sich hier eigene Implementierungen bereitstellen. Ein Beispiel finden Sie in den JDK-7-Demos (<JDK7>/demo/nio/zipfs/src.zip). Zu einem FileSystem gehören möglicherweise auch mehrere FileStores. Sie lassen sich am Standard-FileSystem erfragen:

  FileSystem fs = FileSystems.getDefault();
for (FileStore store : fs.getFileStores()) {
printFileStore(store);
}

Ein einfaches DiskUsage-Beispiel lässt sich nun in weniger als 40 Codezeilen realisieren. (Siehe das Listing aus dem Java-7-Tutorial.)

Eine letzte Neuerung stellt der WatchService dar. Nunmehr lassen sich Veränderungen von Dateien überwachen, indem auf entsprechende Benachrichtigungen reagiert wird. Dazu verwendet man direkt am FileSystem den WatchService und registriert einen WatchKey am Path:

WatchService watchService = FileSystems.getDefault().newWatchService();
WatchKey watchedPath = directory.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
WatchKey key = watchedPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);

Am WatchService lässt sich darüber hinaus via take() ein signalisierter WatchKey abfragen:

WatchKey signalledKey = watchService.take();

Übrig bleibt eine Liste der geschriebenen Events:

List<WatchEvent<?>> list = signalledKey.pollEvents();

Die lassen sich dann auswerten. Wichtig ist, dass man den Überwachungsprozess wieder freigibt (reset()), damit weitere Events erneut benachrichtigt werden. (Das Listing für das WatchService-Beispiel findet man auf dem Heise-FTP-Server (ZIP, 17kB).

Die Performance des NIO.2-APIs ist gewaltig nach oben gegangen. Auf den NIO.2-Slides (PDF) zum offiziellen Java-7-Event am 7. Juli ist das detailliert zu erkennen.

Egal, welches neue Feature von Java 7 man "in die Finger bekommt": Bei der historischen Betrachtung wird schnell klar, dass seine Grundsteine vielfach bereits kurz nach der Verfügbarkei des letzten Java-Release gelegt wurden. Die hier vorgestellten neuen Funktionen von Java 7 führen das bereits Vorhandene im Sinne einer konsequenten Evolution weiter. Von Überraschungen kann daher nicht die Rede sein. Die Erweiterungen im Bereich der Nebenläufigkeit konnten als externe Bibliothek bereits auf Basis von Java 6 eingesetzt werden und sind nun in den Standard umgezogen. Auch die mit NIO.2 eingeführten Funktionen sind schon vergleichsweise lange bekannt. Lediglich die Umsetzung im Rahmen einer neuen Java-Funktion hat diesmal einfach nur viel zeit in Anspruch genommen. Auf die Kopierfunktion beispielsweise haben Java-Entwickler jetzt fast 15 Jahre lang warten müssen.

Markus Eisele
ist Principle IT Architect bei der msg systems AG in München.
()