Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 6 Dateien, Verzeichnisse und Dateizugriffe
Pfeil 6.1 Alte und neue Welt in java.io und java.nio
Pfeil 6.1.1 java.io-Paket mit File-Klasse
Pfeil 6.1.2 NIO.2 und java.nio-Paket
Pfeil 6.2 Dateisysteme und Pfade
Pfeil 6.2.1 FileSystem und Path
Pfeil 6.2.2 Die Utility-Klasse Files
Pfeil 6.2.3 Dateien kopieren und verschieben
Pfeil 6.2.4 Dateiattribute *
Pfeil 6.2.5 Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschen
Pfeil 6.2.6 MIME-Typen herausfinden *
Pfeil 6.2.7 Verzeichnislistings (DirectoryStream/Stream) und Filter *
Pfeil 6.2.8 Rekursives Ablaufen des Verzeichnisbaums *
Pfeil 6.2.9 Rekursiv nach Dateien/Ordnern suchen mit Files.find(…) *
Pfeil 6.2.10 Dateisysteme und Dateisystemattribute *
Pfeil 6.2.11 Verzeichnisse im Dateisystem überwachen *
Pfeil 6.3 Datei- und Verzeichnis-Operationen mit der Klasse File
Pfeil 6.3.1 Dateien und Verzeichnisse mit der Klasse File
Pfeil 6.3.2 Verzeichnis oder Datei? Existiert es?
Pfeil 6.3.3 Verzeichnis- und Dateieigenschaften/-attribute
Pfeil 6.3.4 Umbenennen und Verzeichnisse anlegen
Pfeil 6.3.5 Verzeichnisse auflisten und Dateien filtern
Pfeil 6.3.6 Dateien berühren, neue Dateien anlegen, temporäre Dateien
Pfeil 6.3.7 Dateien und Verzeichnisse löschen
Pfeil 6.3.8 Wurzelverzeichnis, Laufwerksnamen, Plattenspeicher *
Pfeil 6.3.9 URL-, URI- und Path-Objekte aus einem File-Objekt ableiten *
Pfeil 6.3.10 Mit Locking Dateien sperren *
Pfeil 6.3.11 Sicherheitsprüfung *
Pfeil 6.3.12 Zugriff auf SMB-Server mit jCIFS *
Pfeil 6.4 Dateien mit wahlfreiem Zugriff
Pfeil 6.4.1 Ein RandomAccessFile zum Lesen und Schreiben öffnen
Pfeil 6.4.2 Aus dem RandomAccessFile lesen
Pfeil 6.4.3 Schreiben mit RandomAccessFile
Pfeil 6.4.4 Die Länge des RandomAccessFile
Pfeil 6.4.5 Hin und her in der Datei
Pfeil 6.5 Wahlfreier Zugriff mit SeekableByteChannel und ByteBuffer *
Pfeil 6.5.1 SeekableByteChannel
Pfeil 6.5.2 ByteBuffer
Pfeil 6.5.3 Beispiel mit Path + SeekableByteChannel + ByteBuffer
Pfeil 6.5.4 FileChannel
Pfeil 6.6 Zum Weiterlesen
 
Zum Seitenanfang

6.2Dateisysteme und Pfade Zur vorigen ÜberschriftZur nächsten Überschrift

Im Zentrum von NIO.2 stehen die Typen FileSystem und Path:

  • FileSystem beschreibt ein Datensystem und ist eine abstrakte Klasse. Es wird von konkreten Dateisystemen, wie dem lokalen Dateisystem oder einem ZIP-Archiv, realisiert. Um an das aktuelle Dateisystem zu kommen, deklariert die Klasse FileSystems eine statische Methode: FileSystems.getDefault().

  • Path repräsentiert einen Pfad zu einer Datei oder einem Verzeichnis, wobei die Pfadangaben relativ oder absolut sein können. Die Methoden erinnern ein wenig an die alte Klasse File, doch der große Unterschied ist, dass File selbst die Datei oder das Verzeichnis repräsentiert und Abfragemethoden wie isDirectory() oder lastModified() deklariert, während Path nur den Pfad repräsentiert und nur pfadbezogene Methoden anbietet. Modifikationsmethoden gehören nicht dazu; dazu dienen Extra-Typen wie BasicFileAttributes für Attribute.

 
Zum Seitenanfang

6.2.1FileSystem und Path Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Path-Objekt lässt sich nicht wie File über einen Konstruktor aufbauen, da die Klasse abstrakt ist. File und Path haben aber dennoch einiges gemeinsam, etwa dass sie immutable sind. Das FileSystem-Objekt bietet die entsprechende Methode getPath(…), und ein FileSystem wird über eine Fabrikmethode von FileSystems erfragt.

[zB]Beispiel

Baue ein Path-Objekt auf:

FileSystem fs = FileSystems.getDefault();
Path p = fs.getPath( "C:/Windows/Fonts/" );

Da der Ausdruck FileSystems.getDefault().getPath(…) etwas unhandlich ist, existiert eine Utility-Klasse, und der Aufruf von Paths.get() kürzt das Ganze ab. Auch aus einem File-Objekt lässt sich mit toPath() ein Path ableiten. Wir werden die Vereinfachung mit Paths.get(…) im Folgenden nutzen.

final class java.nio.file.Paths
  • static Path get(String first, String... more)
    Erzeuge einen Pfad aus Segmenten. Wenn etwa »\« der Separator ist, dann ist Paths.get("a", "b", "c") gleich Paths.get("a\\b\\c").

  • static Path get(URI uri)
    Erzeugt einen Pfad aus einem URI.

Jedes Path-Objekt hat auch eine Methode getFileSystem(), um wieder an das FileSystem zu kommen.

Abhängigkeiten der Klassen Paths und Path

Abbildung 6.1Abhängigkeiten der Klassen Paths und Path

[»]Hinweis

Der Pfad-String darf unter Java kein \u0000 enthalten, andernfalls gibt es eine Ausnahme. So führt Paths.get("my.php\u0000.jpg") zur Ausnahme »java.nio.file.InvalidPathException: Illegal char < > at index 6: my.php«. Das ist ein wichtiges Sicherheitsmerkmal, um zum Beispiel einen Webserver zu schützen, keine falschen Dateien anzunehmen. Der Dateiname sieht über "my.php\u0000.jpg".endsWith(".jpg") wie eine JPG-Datei aus, aber würde alles nach dem Null-String abgeschnitten (was einige Dateisysteme machen), wäre plötzlich eine Datei my.php angelegt.

Path-Eigenschaften erfragen

Die Path-Klasse deklariert diverse getXXX(…)-Methoden (und eine isAbsolute()-Methode), die eine gewisse Ähnlichkeit mit den Methoden aus File haben. Ein paar Beispiele zur Anwendung:

Listing 6.1com/tutego/insel/nio2/FileSystemPathFileDemo1.java, main()

Path p = Paths.get( "C:/Windows/Fonts/" );
System.out.println( p.toString() ); // C:\Windows\Fonts
System.out.println( p.isAbsolute() ); // true
System.out.println( p.getRoot() ); // C:\
System.out.println( p.getParent() ); // Fonts
System.out.println( p.getNameCount() ); // 2
System.out.println( p.getName(p.getNameCount()-1) ); // Fonts

Methoden wie getPath(), getRoot() und getParent() liefern alle wiederum Path-Objekte aus den Bestandteilen eines gegebenen Pfades. Es gibt drei Methoden, um das Ergebnis nicht als Path weiterzuverarbeiten:

  • toString() liefert eine String-Repräsentation,

  • die Methode toUri() einen URI und

  • toFile() ein traditionelles File-Objekt.

Dadurch, dass Path eine hierarchische Liste von Namen für den Pfad speichert, lässt sich jedes Segment des Pfades erfragen; das ist die Aufgabe von getName(int n), das wiederum einen Path liefert. Die Methode subpath(int beginIndex, int endIndex) liefert einen Path mit den Segmenten des angegebenen Bereichs. Path implementiert die Iterable-Schnittstelle, was eine Methode iterator() vorschreibt – das wiederum bedeutet, dass Path rechts vom Doppelpunkt im erweiterten for auftauchen kann.

Klassendiagramm von Path

Abbildung 6.2Klassendiagramm von Path

Praktisch sind die Prüfmethoden startsWith(Path other) und endsWith(Path other), die feststellen, ob der Pfad mit einem bestimmten anderen Pfad beginnt oder endet. Aus Object wird equals(…) überschrieben. Da Path die Schnittstelle Comparable<Path> realisiert, wird zudem compareTo(Path) implementiert. Die Methode equals(…) löst die Pfade nicht auf, sondern betrachtet nur den Namen; die statische Methode isSameFile(Path, Path) der Klasse Files macht diesen Test und löst relative Bezüge auf. Neben equals(…) überschreibt Path auch hashCode().

interface java.nio.file.Path
extends Comparable<Path>, Iterable<Path>, Watchable
  • String toString()

  • File toFile()

  • URI toUri()

  • Path getFileName()

  • Path getParent()

  • Path getRoot()

  • boolean isAbsolute()

  • int getNameCount()

  • Path getName(int index)

  • Iterator<Path> iterator()

  • Path subpath(int beginIndex, int endIndex)

  • boolean endsWith(Path other)

  • boolean endsWith(String other)

  • boolean startsWith(Path other)

  • boolean startsWith(String other)

  • boolean equals(Object other)

  • int compareTo(Path other)

  • int hashCode()

Vererbungsbeziehung von Path

Abbildung 6.3Vererbungsbeziehung von Path

[»]Hinweis

Die Methode getFileName() liefert keinen String, sondern ein Path-Objekt nur mit dem Dateinamen. Daher führt path.getFileName().endsWith(".xml") zum Testen, ob ein Dateiname auf ».xml« endet, nicht zum Ziel, sondern nur path.getFileName().toString().endsWith(".xml"). Das kann für Umsteiger schnell zu einer Falle werden.

Neue Pfade aufbauen

Die Methode resolve(…) baut neue Pfade zusammen und akzeptiert die Parametertypen String und Path.

[zB]Beispiel

Hänge das Benutzerverzeichnis mit dem Bilderverzeichnis zusammen:

Path picturePath = Paths.get( System.getProperty("user.home") )
.resolve( "Pictures" )
.resolve( "Cora" );
System.out.println( picturePath ); // z. B. C:\Users\Chris\Pictures\Cora

Eine interessante Methode ist auch relativize(Path) – sie liefert aus einer Basisangabe einen relativen Pfad, der zu einem anderen Pfad führt.

[zB]Beispiel

Von c:/Windows/Fonts nach c:/Windows/Cursors führt der relative Pfad ..\Cursors:

System.out.println( Paths.get( "C:/Windows/Fonts" )
.relativize( Paths.get("C:/Windows/Cursors") )
); // ..\Cursors

Der Pfad kann unter Windows mit »/« oder mit »\« angegeben werden.

interface java.nio.file.Path
extends Comparable<Path>, Iterable<Path>, Watchable
  • Path relativize(Path other)

  • Path resolve(Path other)

  • Path resolve(String other)

  • Path resolveSibling(Path other)

  • Path resolveSibling(String other)

Normalisierung und Pfadauflösung

Genauso wie die File-Klasse symbolisiert die Path-Klasse einen Pfad, aber dieser muss nicht auf eine konkrete Datei oder ein konkretes Verzeichnis zeigen. Daher liefern die vorgestellten Methoden lediglich Informationen, die sich aus dem vorgegebenen Namen erschließen lassen, ohne auf das Dateisystem zurückzugreifen. Bei relativen Pfaden liefern die Anfragemethoden daher wenig Spannendes:

Listing 6.2com/tutego/insel/nio2/FileSystemPathFileDemo2.java, main()

Path p = Paths.get( "../.." );
System.out.println( p.toString() ); // ..\..
System.out.println( p.isAbsolute() ); // false
System.out.println( p.getRoot() ); // null
System.out.println( p.getParent() ); // ..
System.out.println( p.getNameCount() ); // 2
System.out.println( p.getName(p.getNameCount()-1) ); // ..

Um ein wenig Ordnung in relative Pfadangaben zu bringen, bietet die Path-Klasse die Methode normalize(), die ohne Zugriff auf das Dateisystem die Bezüge ».« und »..« entfernt.

Zum Auflösen der relativen Adressierung mit Zugriff auf das Dateisystem bietet die Path-Klasse die beiden Methoden toAbsolutePath() bzw. toRealPath(…) an.

Listing 6.3com/tutego/insel/nio2/RealAndAbsolutePath.java, main()

Path p2 = Paths.get( "../.." );
System.out.println( p2.toAbsolutePath() );
// C:\Users\Christian\Documents\Insel\programme\2_06_Files\..\..
try {
System.out.println( p2.toRealPath( LinkOption.NOFOLLOW_LINKS ) );
// C:\Users\Christian\Documents\Insel
}
catch ( IOException e ) { e.printStackTrace(); }

Die erste Methode toAbsolutePath() normalisiert nicht, sondern löst einfach nur den relativen Pfad in einen absoluten Pfad auf. Die Auflösung vom ../.. erledigt toRealPath(LinkOption...), wobei das (optionale) Argument ausdrückt, ob Verknüpfungen verfolgt werden sollen oder nicht.

[zB]Beispiel

Die Methode toRealPath(…) löst eine Ausnahme aus, wenn eine Auflösung eines Pfades zu einer Datei versucht wird, die nicht existiert.

So führt zum Beispiel

Paths.get( "../0x" ).toRealPath( true )

zur »java.nio.file.NoSuchFileException: C:\Users\Chris\0x«. Paths.get( "../0x" ).toAbsolutePath() führt zu keinem Fehler.

interface java.nio.file.Path
extends Comparable<Path>, Iterable<Path>, Watchable
  • Path normalize()

  • Path toAbsolutePath()

  • Path toRealPath(LinkOption... options)

Es gibt noch zwei register(…)-Methoden in Path, doch die haben etwas mit dem Anmelden eines Horchers zu tun, der auf Änderungen im Dateisystem reagiert. Sie werden später vorgestellt.

 
Zum Seitenanfang

6.2.2Die Utility-Klasse Files Zur vorigen ÜberschriftZur nächsten Überschrift

Da die Klasse Path nur Pfade, aber keine Dateiinformationen wie die Länge oder Änderungszeit repräsentiert, und da Path auch keine Möglichkeit bietet, Dateien anzulegen und zu löschen, übernimmt die Klasse Files diese Aufgaben.

Einfaches Einlesen und Schreiben von Dateien

Mit den Methoden readAllBytes(…), readAllLines(…), lines(…) bzw. write(…) kann Files einfach einen Dateiinhalt einlesen oder Strings bzw. ein Byte-Feld schreiben.

Listing 6.4com/tutego/insel/nio2/ListAllLines.java, main()

URI uri = ListAllLines.class.getResource( "/lyrics.txt" ).toURI();
Path p = Paths.get( uri );
System.out.printf( "Datei '%s' mit Länge %d Byte(s) hat folgende Zeilen:%n",
p.getFileName(), Files.size( p ) );
int lineCnt = 1;
for ( String line : Files.readAllLines( p /*, StandardCharsets.UTF_8 vor Java 8 */) )
System.out.println( lineCnt++ + ": " + line );
final class java.nio.file.Files
  • static long size(Path path) throws IOException
    Liefert die Größe der Datei.

  • static byte[] readAllBytes(Path path) throws IOExceptionLiest die Datei komplett in ein Byte-Feld ein.

  • static List<String> readAllLines(Path path) throws IOException(Java 8)

  • static List<String> readAllLines(Path path, Charset cs) throws IOException
    Liest die Datei Zeile für Zeile ein und liefert eine Liste dieser Zeilen. Optional ist die Angabe einer Kodierung, standardmäßig ist es StandardCharsets.UTF_8.

  • static Path write(Path path, byte[] bytes, OpenOption... options) throws IOException
    Schreibt ein Byte-Feld in eine Datei.

  • static Path write(Path path, Iterable<? extends CharSequence> lines, OpenOption... options) throws IOException (Java 8)

  • static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options) throws IOException
    Schreibt alle Zeilen aus dem Iterable in eine Datei. Optional ist die Kodierung, die StandardCharsets.UTF_8 ist, so nicht anders angegeben.

  • static Stream<String> lines(Path path)

  • Stream<String> lines(Path path, Charset cs)
    Liefert einen Stream von Zeilen einer Datei. Optional ist die Angabe der Kodierung, die sonst standardmäßig StandardCharsets.UTF_8 ist. Beide Methoden sind neu in Java 8.

Die Aufzählung OpenOption ist ein Vararg, und daher sind Argumente nicht zwingend nötig. StandardOpenOption ist eine Aufzählung vom Typ OpenOption mit Konstanten wie APPEND, CREATE usw.

[»]Hinweis

Auch wenn es naheliegt, die Files-Methode zum Einlesen mit einem Path-Objekt zu füttern, das einen HTTP-URI repräsentiert, funktioniert dies nicht. So liefert schon die erste Zeile des Programms eine Ausnahme des Typs »java.nio.file.FileSystemNotFoundException: Provider "http" not installed«.

URI uri = new URI( "http://tutego.de/javabuch/aufgaben/bond.txt" );
Path path = Paths.get( uri ); //
List<String> content = Files.readAllLines( path );
System.out.println( content );

Vielleicht kommt in der Zukunft ein Standard-Provider von Oracle, doch es ist davon auszugehen, dass quelloffene Lösungen diese Lücke schließen werden. Schwer zu programmieren sind Dateisystem-Provider nämlich nicht.

Datenströme kopieren

Sollen die Daten nicht direkt aus einer Datei in ein byte-Feld/eine String-Liste gehen bzw. aus einem byte-Feld/einer String-Sammlung in eine Datei, sondern von einer Datei in einen Datenstrom, so bieten sich zwei copy(…)-Methoden an:

final class java.nio.file.Files
  • static long copy(InputStream in, Path target, CopyOption... options)
    Entleert den Eingabestrom und kopiert die Daten in die Datei.

  • static long copy(Path source, OutputStream out)
    Kopiert alle Daten aus der Datei in den Ausgabestrom.

Im Zusammenhang mit Datenströmen kommen wir noch einmal auf diese beiden Methoden zurück.

 
Zum Seitenanfang

6.2.3Dateien kopieren und verschieben Zur vorigen ÜberschriftZur nächsten Überschrift

Zum Kopieren und Verschieben (bzw. Umbenennen) von Dateien und Verzeichnissen bietet die Files-Klasse eine copy(…)- und eine move(…)-Methode:[ 70 ](Damit ist ein mv bin/laden /dev/null nun auch in Java möglich.)

final class java.nio.file.Files
  • static Path copy(Path source, Path target, CopyOption... options) throws IOException

  • static Path move(Path source, Path target, CopyOption... options) throws IOException
    Kopiert oder verschiebt eine Datei. Die Rückgabe ist der Zielpfad.

Da CopyOption in einem Vararg ist, ist der Aufruf ohne Zusatzoptionen sehr einfach – nur ein Zielort muss angegeben werden:

Listing 6.5com/tutego/insel/nio2/FilesCopyAndMoveDemo.java, main()

Path copySourcePath = Paths.get( "src/lyrics.txt" );
Path copyTargetPath = Paths.get( "src/lyrics – Kopie.txt" );
Files.copy( copySourcePath, copyTargetPath );

Path moveSourcePath = Paths.get( "src/lyrics.txt" );
Path moveTargetPath = Paths.get( "bin/lyrics.txt" );
Files.move( moveSourcePath, moveTargetPath );

Die Methoden versuchen die Dateiattribute auf das Ziel zu übertragen; unterstützt das Ziel ein Attribut nicht, kann es sein, dass nur einige Attribute übertragen werden.

Das Kopieren und Verschieben sind Betriebssystemoperationen, und es ist zu erwarten, dass dies schneller ist, als von Hand die Bytes von einer Stelle zur anderen zu kopieren.

[»]Hinweis

Die Methoden copy(…) und move(…) werden nicht parallel im Hintergrund ausgeführt, sondern blockieren so lange, bis die Operation durchgeführt wurde. Wer es parallel haben möchte, der muss einen Hintergrund-Thread nutzen.

Kopier- und Verschiebeoptionen

copy(…) und move(…) führen zu einer IOException, wenn die Dateien/Verzeichnisse nicht kopiert oder verschoben werden konnten. Das ist insbesondere dann wichtig, wenn sich die Dateiattribute nicht übertragen lassen. Das bringt uns zu den optionalen CopyOption-Elementen. CopyOption ist eine Schnittstelle, die von zwei Aufzählungen StandardCopyOption und LinkOption wie folgt implementiert wird:

Listing 6.6java/nio/file/StandardCopyOption.java, StandardCopyOption

public enum StandardCopyOption implements CopyOption {
REPLACE_EXISTING,
COPY_ATTRIBUTES,
ATOMIC_MOVE;
}

Listing 6.7java/nio/file/LinkOption.java, LinkOption

public enum LinkOption implements OpenOption, CopyOption {
NOFOLLOW_LINKS;
}

StandardCopyOption und LinkOption stellen somit gültige Argumente für copy(…) und move(…) dar. Die Bedeutung der Aufzählungselemente ist wie folgt:

Argument

Bedeutung bei copy(…)

Bedeutung bei move(…)

REPLACE_EXISTING

Ersetzt, falls vorhanden, die Datei bzw. das Verzeichnis am Zielort. Ist das Ziel eine existierende symbolische Verknüpfung, so wird nur die Verknüpfung selbst ersetzt, aber nicht die Datei bzw. das Verzeichnis, auf die/das die Verknüpfung zeigt.

COPY_ATTRIBUTES

Versucht, alle Attribute zu kopieren.

NOFOLLOW_LINKS

Ist der Path eine Verknüpfung, so wird nur die Verknüpfung selbst kopiert, aber nicht die Datei, auf die die Verknüpfung zeigt.

ATOMIC_MOVE

Führt das Verschieben (also das Anlegen der Kopie und das Löschen des Originals) atomar durch. Führt zu AtomicMoveNotSupportedException, falls das Dateisystem dies nicht unterstützt.

Tabelle 6.1Konstanten aus StandardCopyOption

Beim Kopieren von symbolischen Verknüpfungen wird standardmäßig die referenzierte Datei kopiert, aber nicht die Verknüpfung. Daher gibt es die Option NOFOLLOW_LINKS. Falls REPLACE_EXISTING nicht angegeben ist und im Zielordner schon eine Datei bzw. ein Verzeichnis existiert, lösen copy(…) und move(…) eine FileAlreadyExistsException aus. Das Kopieren von Dateien ist nicht automatisch atomar, und eine Option lässt sich auch nicht setzen.

Im Fall von Verzeichnissen wird copy(…) nur ein leeres Verzeichnis anlegen, aber nicht die Dateien eines Quellverzeichnisses automatisch mit kopieren. Das muss per Hand übernommen werden; Files.walkFileTree(…) ist für diesen Fall ganz gut geeignet und hilft beim Ablaufen von Verzeichnisbäumen. Die Semantik bei move(…) und nicht leeren Verzeichnissen ist komplizierter, da es hier darauf ankommt, ob es sich um ein Verschieben auf dem lokalen Dateisystem handelt (also um eine Art »Umbenennen«) oder um ein Verschieben auf zum Beispiel ein anderes Laufwerk. Wenn die Einträge in einem Verzeichnis wirklich auf ein anderes Dateisystem verschoben werden müssen, so übernimmt move(…) diese Arbeit nicht; hier müssen wir selbst per copy(…) auf der Ebene der einzelnen Einträge kopieren.

[»]Hinweis

Kommt es während einer Kopier- oder Verschiebeoperation zu Problemen und tritt eine IOException auf, so müssen wir uns um eventuell übrig gebliebene Reste und halb kopierte Dateien kümmern.

 
Zum Seitenanfang

6.2.4DateiattributeZur vorigen ÜberschriftZur nächsten Überschrift

Für das Attribut-Management wurde das Paket java.nio.file.attribute geschaffen. Wer einen Blick auf das Paket wirft, dem fallen eine ganze Reihe von Schnittstellen und ein Namensmuster auf. Zunächst ist es wichtig zu verstehen, dass es auf die Attribute eine Reihe von Sichten für unterschiedliche Betriebssysteme gibt, etwa eine DOS-Sicht oder eine Unix-Sicht. Das gab es bisher unter Java nicht.

Für diese Sichten gibt es jeweils Schnittstellen, die die Dateisystem-Attribute für diese Sicht lesen oder schreiben. So ist es zum Beispiel die Aufgabe der Schnittstelle BasicFileAttributeView, Methoden zu deklarieren, die grundlegende Attribute einlesen und modifizieren, oder die Aufgabe von DosFileAttributeView, einer Unterschnittstelle von BasicFileAttributeView, die Attribute eines DOS-Dateisystems zu lesen und zu schreiben.

Nun kommt für die XXXAttributeView-Schnittstellen je eine Tochterschnittstelle XXXAttributes hinzu. Die Aufgabe dieser XXXAttributes-Schnittstellen ist es, Methoden zum Lesen der Eigenschaften vorzuschreiben. So ist zum Beispiel BasicFileAttributeView mit BasicFileAttributes verbunden und DosFileAttributeView mit BasicFileAttributes. Die angebotenen Methoden bei XXXAttributes sind nicht nach der JavaBeans-Notation als Getter aufgebaut, sondern kurz und knapp. BasicFileAttributes deklariert zum Beispiel creationTime() und size().

Vererbungsbeziehungen bei den AttributeView-Klassen

Abbildung 6.4Vererbungsbeziehungen bei den AttributeView-Klassen

Dateiattribute mit XXXFileAttributes-Typen lesen

Um Dateiattribute zu modifizieren bzw. einzulesen, müssen wir an ein XXXAttributeView- bzw. ein XXXAttributes-Objekt kommen.

Für die XXXAttributes steht die Files.readAttributes(…)-Methode bereit. Ihr wird ein Class-Typ für die Attribut-Schnittstelle (etwa BasicFileAttributes.class) als Typ-Token mitgegeben, und die Rückgabe ist eine Implementierung der Schnittstelle. Damit lassen sich dann die Abfragemethoden aufrufen.

Ein Beispiel soll die einfachen Attribute ausgeben:

Listing 6.8com/tutego/insel/nio2/BasicFileAttributesDemo.java, main()

Path p = Paths.get( "src/lyrics.txt" );
BasicFileAttributes attrs = Files.readAttributes( p, BasicFileAttributes.class );
System.out.println( attrs.isRegularFile() ); // true
System.out.println( attrs.isDirectory() ); // false
System.out.println( attrs.isSymbolicLink() ); // false
System.out.println( attrs.isOther() ); // false
System.out.println( attrs.lastModifiedTime() ); // 2006-05-23T12:36:54Z
System.out.println( attrs.lastAccessTime() ); // 2009-07-17T12:24:33Z
System.out.println( attrs.creationTime() ); // 2006-05-23T12:36:54Z
System.out.println( attrs.size() ); // 14
Klassendiagramm von BasicFileAttributes

Abbildung 6.5Klassendiagramm von BasicFileAttributes

Zusammenfassung der XXXFileAttributes-Schnittstellen und ihrer Methoden

BasicFileAttributes ist eine Oberschnittstelle von DosFileAttributes und PosixFileAttributes mit allgemeinen Methoden wie isDirectory() oder lastAccessTime(). Die Unterschnittstelle DosFileAttributes schreibt Methoden vor, die nur auf DOS-Dateisystemen sinnvoll sind, etwa isSystem(), und PosixFileAttributes fügt insbesondere Operationen zur Abfrage von Rechten hinzu.

Operationen

Funktion

BasicFileAttributes

long size()

Größe der Datei in Bytes

boolean isDirectory()

Verzeichnis?

boolean isOther()

Datei/Verzeichnis/Verknüpfung oder etwas anderes?

boolean isRegularFile()

Normale Datei?

boolean isSymbolicLink()

Symbolische Verknüpfung?

FileTime creationTime()

Erstellungszeit

FileTime lastAccessTime()

Zeit des letzten Zugriffs

FileTime lastModifiedTime()

Zeit der letzten Änderung

Object fileKey()

stabiler eindeutiger Identifizierer der Datei

DosFileAttributes

boolean isArchive()

Archiv-Bit gesetzt?

boolean isHidden()

Hidden-Bit gesetzt?

boolean isReadOnly()

Nur-Lese-Bit gesetzt?

boolean isSystem()

Systemdatei-Bit gesetzt?

PosixFileAttributes

UserPrincipal owner()

Eigentümer

GroupPrincipal group()

Gruppeneigentümer

Set<PosixFilePermission> permissions()

Rechte der Datei

Tabelle 6.2Operationen auf unterschiedlichen XXXFileAttributes-Schnittstellen

Bei Posix-Systemen (Windows wird nicht als solches erkannt) lassen sich die Rechte und der Benutzer erfragen:

Listing 6.9com/tutego/insel/nio2/PosixFileAttributesDemo.java, main()

Path p = Paths.get( "src/lyrics.txt" );
PosixFileAttributes attrs = Files.readAttributes( p, PosixFileAttributes.class );
System.out.println( attrs.group() );
System.out.println( attrs.permissions() );
System.out.println( attrs.owner() );

Die Methode permissions() liefert ein Set<PosixFilePermission>, wobei ein PosixFilePermission ein Aufzählungstyp mit den Konstanten GROUP_EXECUTE, GROUP_READ, GROUP_WRITE, OTHERS_EXECUTE, OTHERS_READ, OTHERS_WRITE, OWNER_EXECUTE, OWNER_READ und OWNER_WRITE ist.

Um die Mengen mit PosixFilePermission-Elementen leicht ausgeben und aufbauen zu können, gibt es eine Utility-Klasse java.nio.file.attribute.PosixFilePermissions. Sie dient der Konvertierung von Rechteangaben in der Unix-Notation.

Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
System.out.println( PosixFilePermissions.toString( perms ) ); // rwxr-x---

Anstatt in unserem Beispiel PosixFileAttributesDemo also einfach nur

System.out.println( attrs.permissions() );

zu nutzen, ist eine andere Schreibweise zu empfehlen:

System.out.println( PosixFilePermissions.toString( attrs.permissions() ) );

FileTime-Klasse

java.nio.file.attribute.FileTime ist eine finale Klasse und repräsentiert Zeitstempel, die von den BasicFileAttributes Folgendes liefern (nicht jedes Betriebssystem liefert alle Werte):

  • creationTime(): Wann wurde die Datei angelegt?

  • lastAccessTime(): Wann wurde die Datei zuletzt angefasst/gelesen?

  • lastModifiedTime(): Wann gab es den letzen Schreibzugriff?

Mit dem FileTime in der Hand können wir die Zeit auslesen mit to(TimeUnit) oder toInstant() (liefert das neu in Java 8 eingeführte java.time.Instant). Sonst lässt sich mit FileTime nicht viel anstellen, es überschreibt aber equals(…) und hashCode() und implementiert Comparable<FileTime>.

FileTime-Objekte lassen sich über statische fromXXX(…)-Methoden aufbauen und dann den Dateiattributen zuweisen, wie wir gleich sehen werden.

Attribute über XXXAttributeView-Schnittstellen schreiben

Die XXXAttributes-Schnittstellen bieten nur Lesemethoden (und das auch nicht in der Getter-Konvention, wie wir gesehen haben), aber Modifikationen müssen auf einem anderen Weg durchgeführt werden. Einfach ist zum Beispiel das Setzen der letzten Zugriffszeit, da Files mit setLastModifiedTime(Path path, FileTime time) für diesen speziellen Fall schon eine Methode mitbringt – das ist aber neben ein paar anderen Methoden eine Besonderheit.

Der Weg, um Attribute zu modifizieren, führt über die XXXAttributeView-Schnittstellen, da sie die entsprechenden Methoden wie setReadOnly() bereithalten. Zur Erinnerung: Bei den XXXAttributes-Typen brauchen wir nicht zu suchen, dort finden sich nur Lesemethoden, wie isReadOnly(). Bleibt die Frage, wer Exemplare dieser XXXAttributeView-Typen zurückgibt. Das ist die Aufgabe einer Methode getFileAttributeView() in Files:

final class java.nio.file.Files
  • static <V extends FileAttributeView>
    V getFileAttributeView(Path path, Class<V> type, LinkOption... options)

Erfragen und modifizieren wir zum Beispiel das Archiv-Flag einer Datei:

Listing 6.10com/tutego/insel/nio2/FileAttributeViewDemo.java, main()

Path p = Paths.get( "src/lyrics.txt" );
DosFileAttributeView fileAttributeView = Files.getFileAttributeView(
p, DosFileAttributeView.class );
System.out.println( fileAttributeView.readAttributes().isArchive() ); // true
fileAttributeView.setArchive( false );
System.out.println( fileAttributeView.readAttributes().isArchive() ); // false
fileAttributeView.setArchive( true );
System.out.println( fileAttributeView.readAttributes().isArchive() ); // true

Alle XXXAttributeView-Implementierungen bieten die Methode readAttributes(), die die Attribute liefert. Es entspricht also

BasicFileAttributes a = Files.getFileAttributeView(
p, BasicFileAttributeView.class ).readAttributes();

der Variante:

BasicFileAttributes a = Files.readAttributes( p, BasicFileAttributes.class );

Das heißt, von einem XXXAttributeView kommen wir zu XXXAttributes, aber nicht umgekehrt.

[»]Hinweis

In BasicFileAttributeView (und somit auch in DosFileAttributeView und PosixFileAttributeView) gibt es zum Setzen der Dateizeiten eine Megamethode setTimes(FileTimelastModifiedTime, FileTime lastAccessTime, FileTime createTime) statt drei kleiner Methoden wie setLastModifiedTime(). Soll ein Glied nicht gesetzt werden, ist hier null zu übergeben.

Attribute-Strings als Schlüssel für einen Attributspeicher

Die Anzahl möglicher Attribute ist im Prinzip unbeschränkt, und Java bietet den Zugriff auf die wichtigsten. Doch da neue Dateisystem-Provider im System angemeldet werden können, die wiederum Attribute ganz anderer Art einführen können, gibt es neben den bekannten XXXAttributes-Schnittstellen auch einen alternativen Weg, um Attributzustände über einen String-Schlüssel zu erfragen und zu verändern. Das ist so, als ob mit der Datei ein Assoziativspeicher verbunden wäre, der sich anfragen lässt. Das gibt große Flexibilität, da eine Implementierung neue Attribute veröffentlichen kann, ohne dass die API (also die XXXAttributes) geändert werden muss. Konkrete Plattformen können etwa die Information anbieten,

  • mit welcher Software eine Datei erstellt wurde (Apple tut das),

  • was das zugewiesene Icon ist,

  • ob die Datei indexiert wurde,

  • und neue Dateisysteme etwa für ZIP-Archive können Detailinformationen wie die CRC-Prüfsumme anbieten, ohne dafür über Methoden einer Java-Schnittstelle gehen zu müssen.

Die bekannten XXXAttributeView-Schnittstellen (nicht XXXAttributes) dokumentieren einen String für den Schlüssel zum Zugriff auf den Attributspeicher. Allerdings sind die Strings nicht als Konstante deklariert, sondern einfach in der Dokumentation genannt. Drei Schnittstellen schauen wir uns näher an:

  • BasicFileAttributeView (sollte für alle Systeme gelten)

  • DosFileAttributeView (Windows)

  • PosixFileAttributeView (POSIX-Systeme, Unix)

Schlüssel

Typ

BasicFileAttributeView (basic)

isRegularFile

Boolean

isDirectory

Boolean

isSymbolicLink

Boolean

isOther

Boolean

fileKey

Object

lastModifiedTime

FileTime

lastAccessTime

FileTime

creationTime

FileTime

size

Long

DosFileAttributeView (dos)

readonly

Boolean

Hidden

Boolean

System

Boolean

Archive

Boolean

PosixFileAttributeView (posix)

Permissions

Set<PosixFilePermission>

Group

GroupPrincipal

Tabelle 6.3Schlüssel für Attributspeicher und erwarteter Typ der Rückgabe

Eine Anfrage an den Schlüssel liefert einen Wert mit unterschiedlichen Typen; Object, Boolean, Long und Set sind klar. Neu ist eine spezielle Klasse java.nio.file.attribute.FileTime für Zeiten, die Comparable<FileTime> erweitert, aber keine Erweiterung von Date oder Calendar ist. Nur mit der Methode long to(TimeUnit unit) lassen sich die Dateizeiten konvertieren. GroupPrincipal repräsentiert einen Benutzernamen bzw. eine Gruppe, und ein UserPrincipalLookupService bietet einen Anfragedienst.

[zB]Beispiel

Chris soll der Besitzer der Datei path sein:

UserPrincipalLookupService upls = FileSystems.getDefault()
.getUserPrincipalLookupService();
UserPrincipal principal = upls.lookupPrincipalByName( "chris" );
Files.setOwner( path, principal );

Zum Lesen der Attribute gibt es einmal eine Methode Files.getAttribute(Path path, String attribute, LinkOption... options), die genau ein Attribut erfragt, und zwei überladene Methoden Files.readAttributes(…), die gleich mehrere Attribute auf einmal laden.

Dazu ein Beispiel, wie die Methode Files.getAttribute(…) diverse Attribute erfragt:

Listing 6.11com/tutego/insel/nio2/AttributesDemo.java, main()

Path p = Paths.get( "src/lyrics.txt" );
System.out.println( Files.getAttribute( p, "basic:isRegularFile" ) ); // true
System.out.println( Files.getAttribute( p, "isDirectory" ) ); // false
System.out.println( Files.getAttribute( p, "isSymbolicLink" ) ); // false
System.out.println( Files.getAttribute( p, "isOther" ) ); // false
System.out.println( Files.getAttribute( p, "fileKey" ) ); // null
System.out.println( Files.getAttribute( p, "lastModifiedTime" ) ); // 2006-05-23T12:36:54Z
System.out.println( Files.getAttribute( p, "lastAccessTime" ) ); // 2009-07-17T12:24:33Z
System.out.println( Files.getAttribute( p, "creationTime" ) ); // 2006-05-23T12:36:54Z
System.out.println( Files.getAttribute( p, "size" ) ); // 14
System.out.println( Files.getAttribute( p, "dos:readonly" ) ); // false
System.out.println( Files.getAttribute( p, "dos:hidden" ) ); // false
System.out.println( Files.getAttribute( p, "dos:system" ) ); // false
System.out.println( Files.getAttribute( p, "dos:archive" ) ); // true

Der Attribut-String hat ein Präfix dos: oder posix:, das, falls es sich nicht gerade um die Standardparameter handelt, mit angegeben wird.

[»]Hinweis

Falls ein Präfix nicht unterstützt wird, ist eine Ausnahme die Folge und die Rückgabe von getAttribute(…) nicht einfach nur null. So löst unter Windows ein Files.getAttribute(p, "posix:permissions") eine »java.lang.UnsupportedOperationException: View 'posix' not available« aus. Welche Sichten das System unterstützt, sagt supportedFileAttributeViews(). So liefert

for ( String fs : FileSystems.getDefault().supportedFileAttributeViews() )
System.out.println( fs );

unter einem Windows-System die Zeichenfolgen acl, basic, owner, user, dos.

Auch das Setzen der Werte ist möglich; dazu dient Files.setAttribute(…). Ein Beispiel:

Listing 6.12com/tutego/insel/nio2/SetDosAttribute.java, main()

Path p = Paths.get( "src/lyrics.txt" );
System.out.println(
Files.readAttributes( p, DosFileAttributes.class ).isArchive() ); // true
Files.setAttribute( p, "dos:archive", false );
System.out.println(
Files.readAttributes( p, DosFileAttributes.class ).isArchive() ); // false
Files.setAttribute( p, "dos:archive", true );
System.out.println(
Files.readAttributes( p, DosFileAttributes.class ).isArchive() ); // true

Benutzerdefinierte Datei-Attribute

Der Typ UserDefinedFileAttributeView fällt ein wenig aus dem Rahmen, da er für benutzerdefinierte Schlüssel-Wert-Eigenschaften steht, die als Metadaten mit einem Dateisystemelement assoziiert werden können. Ohne den Dateiinhalt selbst zu verändern, lassen sich ganz neue Metadaten vermerken. Ein Java-Programm könnte zum Beispiel speichern, wer der letzte zugreifende Benutzer war, oder einen Screenshot der Anwendung anhängen.

Listing 6.13com/tutego/insel/nio2/UserDefinedFileAttributeViewDemo.java, main()

Path p = Paths.get( "src/lyrics.txt" ).toAbsolutePath();
UserDefinedFileAttributeView attrs = Files.getFileAttributeView( p,
UserDefinedFileAttributeView.class );
List<String> attrsList = attrs.list();
System.out.println( attrsList ); // []
attrs.write( "last-user",
ByteBuffer.wrap( "chris".getBytes( StandardCharsets.UTF_8 ) ) );
System.out.println( attrs.list() ); // [last-user]
ByteBuffer attrValue = ByteBuffer.allocate( attrs.size( "last-user" ) );
attrs.read( "last-user", attrValue );
attrValue.rewind();
String value = StandardCharsets.UTF_8.decode( attrValue ).toString();
System.out.println( value ); // chris

Der mit dem Schlüssel assoziierte Wert kann ein beliebiges Byte-Feld sein, etwa aus einem String erzeugt oder einer Grafik. Ein wenig ungelenk ist es jedoch, dass hier nicht der Typ byte[] Verwendung findet, sondern ByteBuffer, eine andere NIO-Klasse. Die Nutzung wird an dieser Stelle nicht weiter vertieft, das Beispiel zeigt eine einfache Nutzung für eine Zeichenfolge.

 
Zum Seitenanfang

6.2.5Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klasse Files deklariert zum Anlegen von neuen Dateien und Verzeichnissen die zwei Methoden createFile(…) und createDirectory(…):

  • static Path createFile(Path path) throws IOException

  • static Path createDirectory(Path dir) throws IOException

Beide Methoden liefern das Path-Objekt zurück, das createFile(…) bzw. createDirectory(…) übergeben wurde. Die Methoden deklarieren throws IOException, wenn die Datei bzw. das Verzeichnis nicht angelegt werden kann, und eine java.nio.file.FileAlreadyExistsException (eine Unterklasse von IOException), wenn die Datei bzw. das Verzeichnis schon existiert.

FileAttribute

Beim Erzeugen neuer Dateien oder Verzeichnisse (auch temporärer) spielen Attribute eine wichtige Rolle. Daher lassen sich den createXXX(…)-Methoden FileAttribute-Argumente übergeben. Sie kommen über ein Vararg, sodass entweder überhaupt keine Attribute mitgegeben werden (das haben wir im oberen Fall so geschrieben), oder eben beliebig viele. Die genaue Deklaration der Methoden lautet:

final class java.nio.file.Files
  • static Path createFile(Path path, FileAttribute<?>... attrs) throws IOException

  • static Path createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException

FileAttribute ist eine Schnittstelle, und bisher gibt es nur eine Stelle in der NIO-API, die Objekte vom Typ FileAttribute liefert: Die Utility-Klasse PosixFilePermissions bietet eine statische Hilfsmethode, die die Menge mit PosixFilePermission-Objekten zurückgibt:

class java.nio.file.attribute.PosixFilePermissions
  • static FileAttribute<Set<PosixFilePermission>> asFileAttribute(
    Set<PosixFilePermission> perms)
    Das Argument für die Methode asFileAttribute(…) ist vom Typ Set<PosixFilePermission>, und dies kann wiederum über EnumSet aufgebaut werden oder über die statische Utility-Methode PosixFilePermissions.fromString(string), die wir schon kennengelernt haben.

Temporäre Dateien und Verzeichnisse anlegen

Vier Methoden der Klasse Files erlauben das Anlegen von temporären Dateien bzw. Verzeichnissen:

final class java.nio.file.Files
  • static Path createTempDirectory(Path dir, String prefix, FileAttribute<?>... attrs)
    throws IOException

  • static Path createTempDirectory(String prefix, FileAttribute<?>... attrs)
    throws IOException

  • static Path createTempFile(Path dir, String prefix, String suffix,
    FileAttribute<?>... attrs) throws IOException

  • static Path createTempFile(String prefix, String suffix, FileAttribute<?>... attrs)
    throws IOException

Misslingt dies, gibt es eine Ausnahme, etwa wenn das Dateisystem keine temporären Dateien zulässt oder die Attribute falsch sind.

Rekursiv alle Verzeichnisse anlegen

Gilt es ein Verzeichnis rekursiv mit vorangehenden Verzeichnissen anzulegen, übernimmt die statische Methode Files.createDirectories(…) diese Aufgabe:

final class java.nio.file.Files
  • static Path createDirectories(Path dir, FileAttribute<?>... attrs)
    throws IOException

Symbolische Verknüpfungen anlegen *

Dem Anlegen von symbolischen Verknüpfungen (symbolischer Link) dient die Methode Files.createSymbolicLink(…):

final class java.nio.file.Files
  • static Path createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)
    throws IOException
    Legt eine neue Verknüpfung an. Der Parameter link ist der Ort, an dem die Verknüpfung angelegt werden soll, und der Parameter target bestimmt den Zielpunkt der Verknüpfung. Die Angabe kann absolut oder relativ sein.

Dem Lesen der symbolischen Verknüpfung dient die Files-Methode readSymbolicLink(Path). Die Methode wie auch createSymbolicLink(…) sind optional, müssen also vom Dateisystem-Provider nicht angeboten werden – nicht jedes Dateisystem hat symbolische Verknüpfungen.

Dateien und Verzeichnisse löschen

Die Klasse Files bietet zum Löschen die zwei Methoden delete(…) und deleteIfExists(…):

final class java.nio.file.Files
  • static void delete(Path path) throws IOException

  • static boolean deleteIfExists(Path path) throws IOException

Steht der Pfad für eine Datei, wird diese gelöscht. Steht er für eine symbolische Verknüpfung, wird nur diese gelöscht, aber nicht die Datei bzw. das Verzeichnis, auf die/das er zeigt. Nur leere Verzeichnisse können gelöscht werden. Nicht leere Verzeichnisse bei einem delete(…)-Aufruf können eine DirectoryNotEmptyException auslösen, allerdings ist dieser Ausnahmetyp von der Implementierung abhängig. Anders als delete() testet deleteIfExists() vorher, ob die Datei bzw. das Verzeichnis existiert. Dieser Test ist allerdings nicht atomar. delete() sollte eine NoSuchFileException auslösen, wenn die Datei bzw. das Verzeichnis nicht vorhanden bzw. nicht gelöscht werden konnte. Gibt es grundsätzliche Probleme, wird immer eine Ausnahme vom Typ IOException ausgelöst und nicht einfach wie bei der File-Methode delete() die Rückgabe false geliefert.

 
Zum Seitenanfang

6.2.6MIME-Typen herausfinden * Zur vorigen ÜberschriftZur nächsten Überschrift

Die Erkennung von Dateitypen spielt eine wichtige Rolle, etwa dann, wenn für einen Dateityp ein Programm zum Betrachten oder Bearbeiten aufgerufen werden soll oder ein passendes Icon für einen Dateityp auf der grafischen Oberfläche auftauchen soll. Relativ früh wurde daher der MIME-Typ (Internet Media Type) eingeführt, der Medientypen kennzeichnet. Die wichtigsten Medientypen sind:

Medientyp

Beispiel mit Subtyp

Bedeutung

text

text/plain, text/xml, text/html

Text

image

image/gif, image/png

Bilder

video

video/mpeg, video/quicktime

Videos

audio

audio/mid, audio/mpeg

Audios

application

application/msword, application/octet-stream

Binärdaten

Tabelle 6.4Einige MIME-Typen

Der MIME-Typ wird im Idealfall nicht nach Dateiendungen ermittelt, da es Systeme gibt, die nicht mit Dateiendungen arbeiten, und Dateiendungen auch mehrdeutig sind. Ein guter MIME-Typ-Erkenner (MIME-Sniffer genannt) schaut daher in die Datei und ermittelt den korrekten Typ – einige Dateisysteme speichern den MIME-Typ auch als Metainformation ab.

NIO.2 bietet FileTypeDetector-Klassen, die MIME-Typen identifizieren können. Alle FileTypeDetector-Objekte werden in einer Liste gesammelt, und wenn es darum geht, den MIME-Typ einer bestimmten Datei zu ermitteln, geht eine Schleife über alle angemeldeten Detektoren und fragt jeden Detektor, ob er den MIME-Typ ermitteln kann. Zugang zu diesem Suchalgorithmus bietet die einfache statische Methode Files.probeContentType(Path).

Listing 6.14com/tutego/insel/nio2/MimeTypeDetector.java, main()

Path path1 = Paths.get( "../lyrics.txt" );
System.out.println( Files.probeContentType( path1 ) ); // text/plain

Path path2 = Paths.get( "C:/Windows/Web/Wallpaper/img1.jpg" );
System.out.println( Files.probeContentType( path2 ) ); // image/jpeg

[»]Hinweis

Wir haben schon gesehen und diskutiert, dass Path-Objekte über URI-Objekte aufgebaut werden können. Doch MIME-Typen von Webressourcen lassen sich so nicht ermitteln:

Path p = Paths.get( new URI( "http://www.tutego.de/index.html" ) );
System.out.println( Files.probeContentType( p ) );

Wie schon erwähnt wurde, führt Paths.get(…) zu einer Ausnahme, da es standardmäßig keinen Provider für das Protokoll HTTP gibt.

Eigene Detektoren können über den Service-Mechanismus eingebunden werden. Die API-Dokumentation bei probeContentType(…) in der Klasse Files gibt dazu Hinweise.

[»]Hinweis

Die Files-API gibt es seit Java 7, und für Java 6 bietet etwa new MimetypesFileTypeMap().getContentType(fileName) eine Alternative – das zu den Bordmitteln von Java. Quelloffene Ergänzungen für MIME-Erkennungen gibt es viele. Eine Empfehlung kann für Apache Tika ausgesprochen werden, Details unter http://tika.apache.org/1.5/detection.html.

 
Zum Seitenanfang

6.2.7Verzeichnislistings (DirectoryStream/Stream) und Filter * Zur vorigen ÜberschriftZur nächsten Überschrift

In der Klasse Files finden sich vier Methoden (eine mehr unter Java 8), um zu einem gegebenen Verzeichnis alle Dateien und Unterverzeichnisse aufzulisten:

final class java.nio.file.Files
  • static Stream<Path> list(Path dir) throws IOException
    Neu in Java 8.

  • static DirectoryStream<Path> newDirectoryStream(Path dir) throws IOException

  • static DirectoryStream<Path> newDirectoryStream(Path dir,
    DirectoryStream.Filter<? super Path> filter) throws IOException

  • static DirectoryStream<Path> newDirectoryStream(Path dir, String glob) throws IOException

Die Rückgabe DirectoryStream<T> ist ein Closeable (und somit AutoCloseable) sowie Iterable<T>, und so unterscheidet sich die Möglichkeit zur Anfrage der Dateien im Ordner grundsätzlich von der Methode list(…) in der Klasse File, die immer alle Dateien in einem Feld auf einmal zurückliefert. Bei einem DirectoryStream wird Element für Element über den Iterator geholt; trotz des Namensanhangs »Stream« ist der DirectoryStream kein Strom im Sinne von java.util.stream. Ein Stream<String> hingegen liefert die kompakte Methode list(Path), sie nutzt intern einen DirectoryStream.

Listing 6.15com/tutego/insel/nio2/Dir.java, main()

try ( DirectoryStream<Path> files =
Files.newDirectoryStream( Paths.get( "c:/" ) ) ) {
for ( Path path : files )
System.out.println( path.getFileName() );
}

Aus der Tatsache, dass die Dateien und Unterverzeichnisse nicht in einem Rutsch geholt werden, leitet sich die Konsequenz ab, dass der DirectoryStream/Stream<String> geschlossen werden muss, da nicht klar ist, ob der Benutzer wirklich alle Dateien abholt oder nach den ersten zehn Einträgen aufhört. Die Schnittstelle DirectoryStream erweitert die Schnittstelle Closeable (und die ist AutoCloseable, weshalb unser Beispiel ein try mit Ressourcen nutzt), und Stream implementiert AutoCloseable, daher ist es guter Stil, den DirectoryStream/Stream am Ende zu schließen, um blockierte Ressourcen freizugeben. try mit Ressourcen gibt immer etwaige Ressourcen frei, auch wenn es beim Ablaufen des Verzeichnisses zu einer Ausnahme kam.

Filtern

Die Methoden list(Path) bzw. newDirectoryStream(Path) bilden die einfachsten Varianten für ein Ablaufen eines Ordnerinhalts; sie liefern immer ungefiltert alle Inhalte. Doch lässt sich die Ergebnisliste filtern, und zwar:

  • über bekannte reguläre Ausdrücke

  • über eine spezielle Syntax, mit der sich zum Beispiel *.txt schreiben lässt

  • über eigene Filterklassen

Neben newDirectoryStream(Path) erlauben zwei weitere parametrisierte newDirectoryStream(…)-Methoden zusätzliche Filter, für list(…) gibt es keine Variante mit Filter. In der Version newDirectoryStream(Path, String) ist das Filterkriterium durch eine Zeichenkette beschrieben. Oracle nutzt hier die so genannte Globbing-Syntax, die an reguläre Ausdrücke erinnert. In der API-Dokumentation von FileSystem sind bei der Methode getPathMatcher(String syntaxAndPattern) einige Beispiele gegeben.

Nutzen wir newDirectoryStream(Path, String), um GIF-, JPG- und PNG-Dateien in einem bestimmten Verzeichnis aufzulisten:

Listing 6.16com/tutego/insel/nio2/ListAllImages.java, main()

String userHomeDir = System.getProperty( "user.home" );
Path picturePath = Paths.get( userHomeDir ).resolve( "Pictures" );

try ( DirectoryStream<Path> files =
Files.newDirectoryStream( picturePath, "*.{gif,jpg,png}" ) ) {
for ( Path path : files )
System.out.println( path.getFileName() );
}

Noch weiter geht beim Filtern die Methode newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter). Hier lässt sich ein Filter frei programmieren. Nutzen wir als Beispiel einen Filter, der alle leeren Dateien in einem Verzeichnis aufspürt:

Listing 6.17com/tutego/insel/nio2/ListZeroFiles.java

package com.tutego.insel.nio2;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class ListZeroFiles {
static final class EmptyFilesFilter implements DirectoryStream.Filter<Path> {
@Override public boolean accept( Path path ) throws IOException {
BasicFileAttributes attrib =
Files.readAttributes( path,BasicFileAttributes.class );

return attrib.isRegularFile() && attrib.size() == 0;
}
}

public static void main( String[] args ) throws IOException {
try ( DirectoryStream<Path> files = Files.newDirectoryStream(
Paths.get( "c:/Windows" ), new EmptyFilesFilter() ) ) {
for ( Path path : files )
System.out.println( path.getFileName() );
}
}
}

Alle drei newDirectoryStream(…)-Methoden arbeiten nichtrekursiv. Für die Suche und den rekursiven Abstieg tief in den Verzeichnisbaum gibt es mit FileVisitor eine andere Möglichkeit. Unschön ist auch, dass bei Files.newDirectoryStream(…) nicht mehrere Filter übergeben werden können und dass die Java-API auch keine Klasse mitbringt, die mehrere Filter zu einem Filter zusammenfasst. Das Gleiche gilbt im Übrigen auch für andere Dateifilter.

DirectoryStream und Filter als innerer Typ

Abbildung 6.6DirectoryStream und Filter als innerer Typ

 
Zum Seitenanfang

6.2.8Rekursives Ablaufen des Verzeichnisbaums * Zur vorigen ÜberschriftZur nächsten Überschrift

Die Utility-Klasse Files bietet vier statische Methoden (zwei mehr in Java 8), die, bei einem Startordner beginnend, die Verzeichnisse rekursiv ablaufen:

final class java.nio.file.Files
  • static Stream<Path> walk(Path start, FileVisitOption... options) throws IOException
    Neu in Java 8.

  • static Stream<Path> walk(Path start, int maxDepth, FileVisitOption... options) throws IOException
    Neu in Java 8.

  • static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)

  • static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth,
    FileVisitor<? super Path> visitor)

Bei allen Varianten bestimmt der erste Parameter den Startordner. Während walk(…)einen java.util.stream.Stream liefert (die Methoden sind neu in Java 8), erwarten die anderen beiden walkFileTree(…)-Methoden ein Objekt mit Callback-Methoden, die walkFileTree(…) beim Ablaufen des Verzeichnisbaums aufruft. Dieses Objekt implementiert die Schnittstelle FileVisitor und hat folgende Methodendeklarationen:

interface java.nio.file.FileVisitor<T>
  • FileVisitResult postVisitDirectory(T dir, IOException exc)

  • FileVisitResult preVisitDirectory(T dir)

  • FileVisitResult visitFile(T file, BasicFileAttributes attrs)

  • FileVisitResult visitFileFailed(T file, IOException exc)

Die Operation visitFile(…) ist die wichtigste. Ihr übergibt walkFileTree(…) beim internen Ablaufen den Pfad auf die gefundene Datei bzw. den Ordner (es wird sich in der Regel immer um FileVisitor<Path> handeln) sowie die BasicFileAttributes, die es einfach machen, Attribute wie die Dateigröße ohne große Umwege auszuwerten.

Die aufgerufenen Methoden bestimmen über die Rückgabe, ob der Durchlauf fortgeführt oder abgebrochen wird. FileVisitResult ist eine Aufzählung mit den vier folgenden Konstanten: CONTINUE, SKIP_SIBLINGS, SKIP_SUBTREE, TERMINATE.

Von FileVisitor<T> gibt es mit SimpleFileVisitor<T> eine Standardimplementierung mit folgendem Verhalten:

Methode

Implementierung

preVisitDirectory

return FileVisitResult.CONTINUE;

visitFile

return FileVisitResult.CONTINUE;

visitFileFailed

throw exc;

postVisitDirectory

if (exc != null) throw exc;
return FileVisitResult.CONTINUE;

Tabelle 6.5Methoden aus SimpleFileVisitor

Kommt es also beim Ablaufen zu einem Fehler, führt dies beim SimpleFileVisitor zur IOException, und der Durchlauf bricht ab.

SimpleFileVisitor ist eine Unterklasse von FileVisitor

Abbildung 6.7SimpleFileVisitor ist eine Unterklasse von FileVisitor

Geschichtliches

In den ersten Versionen von NIO.2 hatte die Schnittstelle FileVisitor eine weitere Operation, preVisitDirectoryFailed(…), die immer dann aufgerufen wurde, wenn ein Verzeichnis nicht betreten werden konnte. Diese Methode ist in der finalen Java-API nicht mehr zu finden, denn auch im Fall eines nicht besuchbaren Verzeichnisses ruft der File-Visitor visitFileFailed(…) auf.

Finde alle Bilder (und auch Nemo)

In einem Beispiel wollen wir einen Verzeichnisbesucher schreiben, der alle Bilder ab einem Startverzeichnis findet:

Listing 6.18com/tutego/insel/nio2/CrawlForImages.java, main()

Files.walkFileTree( Paths.get( System.getProperty( "user.home" ) ),
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFileFailed( Path file, IOException exc ) {
return FileVisitResult.SKIP_SUBTREE;
}

@Override
public FileVisitResult visitFile( Path path, BasicFileAttributes attribs ) {
try {
String mime = Files.probeContentType( path );
if ( mime != null && mime.startsWith( "image/" ) )
System.out.println( path );
}
catch ( IOException e ) { }

return FileVisitResult.CONTINUE;
}
} );

Vom SimpleFileVisitor überschreiben wir zwei Methoden. In visitFile(…) testen wir mit dem MIME-Typ-Erkenner, ob es sich um eine Grafik handelt. In diesem Fall beginnt der String mit »image/«. Dass wir auch visitFileFailed(…) überschreiben, hat den Hintergrund, dass das Standardverhalten von SimpleFileVisitor mit dem Auslösen einer Ausnahme bei Fehlern zu einschränkend ist. Damit verhindern wir zum Beispiel einen Komplettabbruch bei einer java.nio.file.AccessDeniedException, die immer dann ausgelöst wird, wenn Zugriffsrechte fehlen. Wir wollen nicht, dass die Abarbeitung gänzlich beendet wird und die JVM mit einem Error abbricht, sondern wir wollen diesen Unterbaum, den wir nicht besuchen können, einfach überspringen. Daher liefert unser visitFileFailed(…) im Fehlerfall nur SKIP_SUBTREE, was in unserer Implementierung auch bedeutet, dass die Suche gleich im Baum abgebrochen wird, wenn nur ein Dateizugriff zu einem Fehler führt. Sollte die Suche bei einem Fehler komplett beendet werden, ist die Rückgabe TERMINATE zu setzen.

Zyklen erkennen, Verknüpfungen verfolgen, Tiefen angeben

Die einfachen statischen Methoden walk(Path) und Files.walkFileTree(Path, FileVisitor) laufen den Verzeichnisbaum bis in eine beliebige Tiefe ab.[ 71 ](Nur theoretisch durch Integer.MAX_VALUE beschränkt.) Zudem folgen sie standardmäßig keinen Verknüpfungen. Für einfache Durchsuchungen ist die Methode gut geeignet, aber wer mehr Gestaltungsraum sucht, der greift zu einer alternativen statischen Methode:

  • Stream<Path> walk(Path start, int maxDepth, FileVisitOption... options)(seit Java 8) bzw.

  • Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)

Das Startverzeichnis und die Rückgabe Stream bzw. der Parameter FileVisitorbleiben, neu sind die maximale Suchtiefe in Verzeichnisebenen (nicht in der Anzahl der Dateien) und Aufzählungselemente, die bestimmen, ob Zyklen erkannt werden und ob symbolischen Verknüpfungen gefolgt werden soll.

FileVisitOption ist eine Aufzählung mit den Konstanten DETECT_CYCLES und FOLLOW_LINKS. Bisher kommt in der Java-API der Argumenttyp »Menge von Aufzählungselementen« (Set<FileVisitOption> bei walkFileTree(…)) selten vor. Hier müssen sich Entwickler zurückerinnern, wie ein EnumSet (Menge bestehend aus Aufzählungen) einfach aufgebaut werden kann. Dazu einige Beispiele:

Path p = …;
FileVisitor<? super Path> v = …;
Files.walkFileTree( p, EnumSet.of( FileVisitOption.DETECT_CYCLES ), 2, v );
Files.walkFileTree( p, EnumSet.of( FileVisitOption.DETECT_CYCLES,
FileVisitOption.FOLLOW_LINKS ), 2, v );
Files.walkFileTree( p, EnumSet.allOf( FileVisitOption.class ), 2, v );

Die einfache Methode walkFileTree(Path start, FileVisitor<? super Path> visitor) ist übrigens auch nur eine Weiterleitung mit walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, visitor). Die einfachere walk(…)-Methode nutzt ein Vararg statt der Menge.

 
Zum Seitenanfang

6.2.9Rekursiv nach Dateien/Ordnern suchen mit Files.find(…) * Zur vorigen ÜberschriftZur nächsten Überschrift

Neu in Java 8 ist die Methode find(…) in Files. Damit sind Dateien nach gewissen Kriterien zu finden:

final class java.nio.file.Files
  • static Stream<Path> find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption... options)
    throws IOException
    Sucht einen Verzeichnisbaum rekursiv ab und wendet auf jeden Path den Filter (Prädikat) an. Falls der Filter zusagt, kommt der Path in den Ergebnis-Stream.

[zB]Beispiel und Hinweis

Finde alle Ordner unter dem Windows-Standard-Bilderverzeichnis, und gib sie aus:

Files.find( Paths.get( System.getProperty( "user.home" ) )
.resolve( "Pictures" ),
Integer.MAX_VALUE,
(p,attr) -> Files.isReadable( p ) && attr.isDirectory()
).forEach( System.out::println );

Intern greift find(…) auf den gleichen Mechanismus wie walk(…) zurück, doch ist eine Eigenimplementierung mithilfe von walk(…) mitunter besser, da wir beim visitFileFailed(…) Fehler ignorieren können – bei find(…) führen Fehler direkt zum Abbruch. Bei Windows führt eine rekursive Suche schnell zu einer java.nio.file.AccessDeniedException durch einen Ordner, an den Java nicht heran darf, und dann ist mit find(…) sofort Schluss.

 
Zum Seitenanfang

6.2.10Dateisysteme und Dateisystemattribute * Zur vorigen ÜberschriftZur nächsten Überschrift

Dateisysteme werden durch den Typ FileSystem beschrieben, und die Utility-Klasse FileSystems bietet die wichtige Methode getDefault(), die das Standard-Dateisystem zurückgibt. Da es aber unterschiedliche Dateisysteme geben kann – und jedes durch eine Implementierung von FileSystemProvider realisiert wird –, müssen sie unterscheidbar sein. Dafür gibt es einen URI, bei dem das Protokoll ausschlaggebend ist.

[zB]Beispiel

Laufe alle installierten FileSystemProvider ab, und gib die unterstützten Protokolle aus:

for ( FileSystemProvider p : FileSystemProvider.installedProviders() )
System.out.println( p.getScheme() );

Die Ausgabe ergibt »file« und »jar«, woran wir ablesen können, dass NIO.2 das Standard-Dateisystem sowie Java- bzw. ZIP-Archive als Dateisystem unterstützt. Die internen realisierenden Klassen sind sun.nio.fs.WindowsFileSystemProvider und com.sun.nio.zipfs.ZipFileSystemProvider, wobei der ZIP-Provider aus jdk1.8.0\jre\lib\ext\zipfs.jar stammt.

Neue Dateisysteme lassen sich zum Beispiel für die Protokolle http, svn, memory[ 72 ](https://github.com/marschall/memoryfilesystem) usw. aufbauen.

Implementierung

Die Aufrufe FileSystems.getDefault() und FileSystems.getFileSystem(new URI("file:/")) führen unter Windows zum gleichen Ergebnis: zur Klasse sun.nio.fs.WindowsFileSystem.

Mit einem FileSystem lässt sich dann über die bekannte Methode getPath(String first, String… more) ein Pfad erfragen. Ob ein Dateisystem nur lesbar ist, beantwortet isReadOnly(). Da es unterschiedliche Pfadtrenner je nach Dateisystem geben kann, liefert getSeparator() einen String mit dem Separator.

Eine weitere Methode ist getRootDirectories(), die ein Iterable<Path> für die Wurzelverzeichnisse liefert.

[zB]Beispiel

Gib alle Wurzelverzeichnisse aus:

for ( Path root : FileSystems.getDefault().getRootDirectories() )
System.out.println( root );

Die Ausgabe könnte C:\ und D:\ sein.

Eine ZIP-Datei komplett ablaufen

Da das JDK einen Dateisystem-Provider für ZIP-Archive mitbringt, lassen sich ZIP-Archive genauso ablaufen wie »normale« Dateisysteme. Unser nächstes Beispiel soll das rt.jar – Java-Archive sind ZIP-Archive – ablaufen. Das sieht so aus:

Listing 6.19com/tutego/insel/nio2/WalkZipFile.java, main()

Path path = Paths.get( System.getProperty( "java.home" ), "lib", "rt.jar" );
try ( FileSystem fs = FileSystems.newFileSystem( path, null ) ) {
Files.walkFileTree( fs.getPath( "/" ), new SimpleFileVisitor<Path>() {
String indent = "";

@Override
public FileVisitResult visitFile( Path path, BasicFileAttributes attrs ) {
System.out.println( " " + indent + path );
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult preVisitDirectory( Path path, BasicFileAttributes attrs ) {
System.out.println( "cd " + indent + path );
indent += " ";
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory( Path path, IOException ioe ) {
indent = indent.substring( 2 );
return FileVisitResult.CONTINUE;
}
} );
}

Die ZIP-Datei bildet eine Ressource, die geschlossen werden muss. Daher kommt der Aufbau in ein try mit Ressourcen. Der Ausdruck Paths.get(System.getProperty("java.home"), "lib", "rt.jar") liefert über eine Systemeigenschaft einen kompletten Pfad zur rt.jar-Datei.

Der Ablauf führt zur folgenden (gekürzten) Ausgabe:

cd /
cd /sunw/
cd /sunw/util/
/sunw/util/EventObject.class
/sunw/util/EventListener.class
cd /sunw/io/
/sunw/io/Serializable.class
cd /sun/
cd /sun/util/
/sun/util/PreHashedMap.class
...
cd /com/oracle/
cd /com/oracle/net/
/com/oracle/net/Sdp.class
/com/oracle/net/Sdp$SdpSocket.class
/com/oracle/net/Sdp$1.class
cd /META-INF/
/META-INF/MANIFEST.MF

[»]Hinweis

Damit NIO.2 eine ZIP-Datei auch anlegen kann, muss eine spezielle Eigenschaft[ 73 ](Bisher gibt es nur zwei Eigenschaften: create und encoding (http://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemproviderprops.html). Kompressionsgrad oder Ähnliches lässt sich nicht angeben.) gesetzt werden:

URI p = Paths.get( "c:/Users/Christian/Dropbox/jokes.zip" ).toUri();
URI uri = URI.create( "jar:" + p );
Map<String,String> env = new HashMap<>();
env.put( "create", "true" );
try ( FileSystem zipfs = FileSystems.newFileSystem( uri, env ) ) {
Files.write( zipfs.getPath( "/j1.txt" ),
"The truth is out there. Anybody got the URL?".getBytes() );
Files.write( zipfs.getPath( "/j2.txt" ),
"The more I C, the less I see.".getBytes() );
}

FileStore und Attribute eines Dateisystems

Die Methode getRootDirectories() liefert nur Path-Objekte, aber sonst keine weiteren Informationen zum Dateisystem. Die physikalischen Eigenschaften lassen sich auch nicht über das FileSystem-Objekt erfragen, sondern sind in eine Extraklasse FileStore ausgelagert. Die FileSystem-Methode getFileStores() liefert eine Iteration über die FileStore-Objekte.

Klassendiagramm von FileStore

Abbildung 6.8Klassendiagramm von FileStore

Listing 6.20com/tutego/insel/nio2/FileStoreDemo.java, main()

for ( FileStore store : FileSystems.getDefault().getFileStores() ) {
long total = store.getTotalSpace() >> 30;
long available = store.getUsableSpace() >> 30;
System.out.println( store + " " + store.name() + " " +
available + " GiB frei von " + total + " GiB" +
", Typ " + store.type() );
}

Mit dem Verschiebeoperator >> 30 bekommen wir gerade die Umrechnung von Byte nach Gibibyte (also Gigabyte, aber binär gesehen), denn 2^30 Byte = 1.073.741.824 Byte. Die Ausgabe kann dann etwa sein:

(C:) 50 GiB frei von 99 GiB, Typ NTFS
(D:) 13 GiB frei von 122 GiB, Typ NTFS
 
Zum Seitenanfang

6.2.11Verzeichnisse im Dateisystem überwachen * Zur vorigen ÜberschriftZur nächsten Überschrift

Schreibt eine Anwendung etwa Log-Dateien in ein Verzeichnis und soll ein anderes Programm dies erkennen, so ist eine Lösung, ständig das Verzeichnis abzulaufen und nach Änderungen zu suchen. Allerdings ist das ziemlich informant, und es gibt in der Java-Bibliothek eine besser Lösung, die (im Idealfall) mit Betriebssystemunterstützung aufwarten kann: Ein WatchService überwacht ein bestimmtes Verzeichnis, arbeitet aber nicht rekursiv. Die Implementierung bekommt diese Ereignisse vom Betriebssystem, welches die Änderungen an Java weitergibt. Daher ist es auch nicht möglich, entfernte Dateisysteme zu überwachen – unter Windows etwa die, die über einen UNC-Pfad angegeben sind –, da die Dateisystem-Ereignisse nicht an andere Rechner verteilt werden.

Ein Beispiel, um auf Änderungen im Verzeichnis C:\ zu reagieren:

Listing 6.21com/tutego/insel/nio2/WatchServiceDemo.java, main()

WatchService watcher = FileSystems.getDefault().newWatchService();

Paths.get( "C:/" ).register( watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY );

while ( true ) {
WatchKey key = watcher.take();
System.out.println( "Änderung" );

for ( WatchEvent<?> event : key.pollEvents() )
System.out.println( "Kind: " + event.kind() + ", Path: " + event.context() );

key.reset();
}

Ein Ablauf kann so aussehen:

Änderung
Kind: ENTRY_CREATE, Path: tutego – Kopie.log
Kind: ENTRY_MODIFY, Path: tutego – Kopie.log
Änderung
Kind: ENTRY_MODIFY, Path: tutego – Kopie.log
Änderung
Kind: ENTRY_DELETE, Path: tutego – Kopie.log
Kind: ENTRY_CREATE, Path: tutego2.log
Kind: ENTRY_MODIFY, Path: tutego2.log
Änderung
Kind: ENTRY_DELETE, Path: tutego2.log

Die WatchService-API

Ein WatchService überwacht Änderungen an Watchable-Objekten. Path ist bisher die einzige Klasse, die die Schnittstelle Watchable implementiert. Grundsätzlich ist der Überwachungsdienst nicht an Dateien und Verzeichnisse gebunden, doch Oracle hat WatchService und Watchable in das Paket java.nio.file gelegt. Es bleibt abzuwarten, ob Oracle diese API auch noch für andere Dinge nutzen wird.

Ein Watchable-Objekt, wie also unser Path, meldet sich mit register(…) an einem WatchService an. Der WatchService stammt von FileSystems.getDefault().newWatchService(). Da es verschiedene Ereignistypen gibt, ist register(…) mit einem Varargs-Parametertyp deklariert, der WatchEvent.Kind fordert: die Aufzählung StandardWatchEventKinds deklariert drei mögliche WatchEvent.Kind-Typen.

Nach der Registrierung wird der Watcher nach den angefallenen Ereignissen gefragt. Hier gibt es eine Variante mit take(), die blockierend wartet, oder poll(), das null liefert, falls keine Ereignisgruppe vorliegt. Mit close() lässt sich der WatchService beenden.

Das mit take() oder poll() entnommene Element ist vom Typ WatchKey. Ein WatchKey ist eine Art Gruppe aus einzelnen WatchEvents. Auf einem WatchKey-Objekt liefert pollEvents() genau diese List<WatchEvent<?>>. Vom WatchEvent erfragt kind() den WatchEvent.Kind, liefert also etwa StandardWatchEventKinds.ENTRY_CREATE, und context() liefert das Objekt, auf das es sich bezog. Bei Dateisystemen dürfte context() immer ein Path liefern, aber der Rückgabetyp von context() ist mit T parametrisiert, was uns allerdings nicht hilft, denn key.pollEvents() liefert nur einen WatchEvent<?>, also ohne Typ. Hier zeigt sich, dass Wildcards in der Rückgabe nicht besonders hilfreich sind.

[»]Hinweis

Der Watch-Service hat mit ENTRY_CREATE, ENTRY_DELETE und ENTRY_MODIFY nur drei Anzeigemöglichkeiten. Das heißt, dass gewisse Dinge nicht kodiert werden können. Ein Umbenennen ergibt zum Beispiel eine Sequenz von drei Ereignissen, aber es gibt kein StandardWatchEventKinds.LÖSCHEN:

Kind: ENTRY_DELETE, Path: Datei

Kind: ENTRY_CREATE, Path: umbenannte Datei

Kind: ENTRY_MODIFY, Path: umbenannte Datei

Wird eine Datei verschoben, so entspricht das einem Löschen, und es gibt nur ein Ereignis:

Kind: ENTRY_DELETE, Path: zu löschende Datei

Der Watch-Service kann nicht sagen, wohin die Datei verschoben wurde, da er immer mit einem konkreten Verzeichnis assoziiert ist – die Lösung, an jedes Verzeichnis einen Watch-Service anzuhängen, ist auch nicht optimal. Wenn Dateien auf dem Dateisystem mit (Strg) + (X) ausgeschnitten werden, ist noch keine Dateioperation geschehen, denn das Ausschneiden ist nur eine Explorer-Aktion; daher gibt es auch kein Ereignis, sondern erst, wenn die Datei mit (Strg) + (V) dann tatsächlich verschoben wird. Das Gleiche gilt bei Drag & Drop. Das Ziehen selbst ist nur ein »Trick« vom Explorer, aber am Dateisystem gibt es erst dann eine Operation, wenn die Datei wirklich fallen gelassen wird.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2018
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern