Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Neues in Java 7
2 Threads und nebenläufige Programmierung
3 Datenstrukturen und Algorithmen
4 Raum und Zeit
5 Dateien, Verzeichnisse und Dateizugriffe
6 Datenströme
7 Die eXtensible Markup Language (XML)
8 Dateiformate
9 Grafische Oberflächen mit Swing
10 Grafikprogrammierung
11 Netzwerkprogrammierung
12 Verteilte Programmierung mit RMI
13 RESTful und SOAP Web-Services
14 JavaServer Pages und Servlets
15 Applets
16 Datenbankmanagement mit JDBC
17 Technologien für die Infrastruktur
18 Reflection und Annotationen
19 Dynamische Übersetzung und Skriptsprachen
20 Logging und Monitoring
21 Java Native Interface (JNI)
22 Sicherheitskonzepte
23 Dienstprogramme für die Java-Umgebung
Stichwort
Download: - openbook, ca. 21,3 MB
Buch bestellen
Ihre Meinung?
<< zurück | Galileo Computing / <openbook> / Java 7 - Mehr als eine Insel | vor >>
Java 7 - Mehr als eine Insel von Christian Ullenboom Das Handbuch zu den Java SE-Bibliotheken
Java 7 - Mehr als eine Insel Galileo Computing 1433 S., 2012, geb. 49,90 Euro, ISBN 978-3-8362-1507-7 |
| 5 Dateien, Verzeichnisse und Dateizugriffe
|  | 5.1 Datei und Verzeichnis
|  |  | 5.1.1 Dateien und Verzeichnisse mit der Klasse File
|  |  | 5.1.2 Verzeichnis oder Datei? Existiert es?
|  |  | 5.1.3 Verzeichnis- und Dateieigenschaften/-attribute
|  |  | 5.1.4 Umbenennen und Verzeichnisse anlegen
|  |  | 5.1.5 Verzeichnisse auflisten und Dateien filtern
|  |  | 5.1.6 Dateien berühren, neue Dateien anlegen, temporäre Dateien
|  |  | 5.1.7 Dateien und Verzeichnisse löschen
|  |  | 5.1.8 Verzeichnisse nach Dateien iterativ durchsuchen *
|  |  | 5.1.9 Wurzelverzeichnis, Laufwerksnamen, Plattenspeicher *
|  |  | 5.1.10 URL-, URI- und Path-Objekte aus einem File-Objekt ableiten *
|  |  | 5.1.11 Mit Locking Dateien sperren *
|  |  | 5.1.12 Sicherheitsprüfung *
|  |  | 5.1.13 Zugriff auf SMB-Server mit jCIFS *
|  | 5.2 Dateien mit wahlfreiem Zugriff
|  |  | 5.2.1 Ein RandomAccessFile zum Lesen und Schreiben öffnen
|  |  | 5.2.2 Aus dem RandomAccessFile lesen
|  |  | 5.2.3 Schreiben mit RandomAccessFile
|  |  | 5.2.4 Die Länge des RandomAccessFile
|  |  | 5.2.5 Hin und her in der Datei
|  | 5.3 Dateisysteme unter NIO.2
|  |  | 5.3.1 FileSystem und Path
|  |  | 5.3.2 Die Utility-Klasse Files
|  |  | 5.3.3 Dateien kopieren und verschieben
|  |  | 5.3.4 Dateiattribute *
|  |  | 5.3.5 Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschen
|  |  | 5.3.6 MIME-Typen testen *
|  |  | 5.3.7 Verzeichnislistings (DirectoryStream) und Filter *
|  |  | 5.3.8 Rekursive Abläufe des Verzeichnisbaums (FileVisitor) *
|  |  | 5.3.9 Dateisysteme und Dateisystemattribute *
|  |  | 5.3.10 Verzeichnisse im Dateisystem überwachen *
|  | 5.4 Wahlfreier Zugriff mit SeekableByteChannel und ByteBuffer *
|  |  | 5.4.1 SeekableByteChannel
|  |  | 5.4.2 ByteBuffer
|  |  | 5.4.3 Beispiel mit Path + SeekableByteChannel + ByteBuffer
|  |  | 5.4.4 FileChannel
|  | 5.5 Zum Weiterlesen
## 5.3 Dateisysteme unter NIO.2 Die bisher vorgestellten Konzepte gibt es im Wesentlichen schon seit den Urzeiten von Java, also seit Java 1.0. In den letzten Jahren ist rund um die File-Klasse wenig passiert. Doch Entwickler quälten sich immer wieder mit ganz zentralen Fragen, die die bisherigen Implementierungen nicht wirklich lösten: Wie lässt sich eine Datei einfach und schnell kopieren? Wie lässt sich eine Datei verschieben, wobei die Semantik auf unterschiedlichen Plattformen immer gleich ist. Wie lässt sich auf eine Änderung im Dateisystem reagieren, sodass ein Callback uns informiert, dass sich eine Datei verändert hat? Wie lässt sich einfach ein Verzeichnis rekursiv ablaufen? Wie lässt sich eine symbolische Verknüpfung anlegen und verfolgen? Wie lässt sich realisieren, dass die File-Operationen abstrahiert werden und nicht nur auf dem lokalen Dateisystem basieren? Wünschenswert ist eine Abstraktion, sodass die gleiche API auch ein virtuelles Dateisystem im Hauptspeicher, entfernte Dateisysteme wie FTP oder ein Repository anspricht. Diese Probleme wurden für Java 7 angegangen und in der JSR-203, »More New I/O APIs for the JavaTM Platform ("NIO.2")«, spezifiziert. Die JSR began schon 2003, und so waren die Erwartungen der Java-Community groß, dass sie nicht so lange warten müssten. Aber erst in Java 7 kam es zum großen Wurf. Das macht die »alte« File-Klasse eigentlich überflüssig, aber vermutlich scheut sich Oracle davor, ein @Deprecated an die Klasse zu setzen, denn sonst würden plötzlich riesige Mengen Quellcode in vielen Programmen markiert. ### 5.3.1 FileSystem und Path Im Zentrum der in Java 7 und NIO.2 eingeführten neuen Klassen stehen FileSystem und Path. Die neuen Typen befinden sich im Gegensatz zu File, das im java.io-Paket liegt, im Paket java.nio.file. Es gibt zwar einige Überlappungen, doch NIO.2 ist mehr oder weniger komplett vom »alten« Modell getrennt: 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 File, doch der große Unterschied ist, dass File selbst die Datei oder das Verzeichnis repräsentiert und Anfragemethoden wie isDirectory() oder lastModified() deklariert, während Path nur den Pfad repräsentiert und nur pfad-bezogene Methoden anbietet. Modifikationsmethoden gehören nicht dazu; dazu dienen extra Typen wie BasicFileAttributes für Attribute. #### Ein Path-Objekt aufbauen 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.
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 die Methode get() in der Utility-Klasse Paths. Auch aus einem File-Objekt lässt sich mit toPath() ein Path ableiten, was bedeutet, dass Oracle für Java 7 noch einmal die File-Klasse angefasst und auf die neuen NIO.2-Klassen angepasst hat. 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 einer URI. Jedes Path-Objekt hat auch eine Methode getFileSystem(), um wieder an das FileSystem zu kommen. Abbildung 5.4: Abhängigkeiten der Klassen Paths und Path #### Path-Eigenschaften erfragen Die Path-Klasse deklariert diverse getXXX()- und isXXX()-Methoden, die eine gewisse Ähnlichkeit mit den Methoden aus File haben. Ein paar Beispiele zur Anwendung: Listing 5.15: com/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() eine 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 Bereiches. Path implementiert die Iterable-Schnittstelle, was eine Methode iterator() vorschreibt – das wiederum bedeutet, dass Path rechts vom Doppelpunkt im erweiterten for auftauchen kann. 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() überschrieben. Die Methode equals() löst die Pfade nicht auf, sondern betrachtet nur den Namen; die Methode isSameFile() der Klasse Files macht diesen Test und löst relative Bezüge auf. Neben equals() überschreibt Path auch hashCode(). Abbildung 5.5: Klassendiagramm von Path
    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() Abbildung 5.6: Vererbungsbeziehung 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 mit ».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 Mit der Methode resolve() lassen sich neue Pfade zusammenhängen. Die überladene Methode akzeptiert die Parametertypen String und Path.
Beispiel
Hänge das Benutzerverzeichnis mit dem Bilderverzeichnis zusammen:
    Path picturePath = Paths.get( System.getProperty("user.home") )
                            .resolve( "Pictures" )
                            .resolve( "Juvy" );
    System.out.println( picturePath ); // z. B. C:\Users\Chris\Pictures\Juvy
Eine interessante Methode ist auch relativize() – sie liefert aus einer Basisangabe einen relativen Pfad, der zu einem anderen Pfad führt.
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
    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 liefen 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 5.16: com/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 5.17: com/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(), wobei das Argument ausdrückt, ob Verknüpfungen verfolgt werden sollen oder nicht.
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. ### 5.3.2 Die Utility-Klasse Files Da die Klasse Path nur Pfade, aber keine Dateiinformationen wie die Länge oder Änderungszeit repräsentiert und Path auch keine Möglichkeit bietet, Dateien anzulegen und zu löschen, übernimmt die Klasse Files diese Aufgaben. #### Einfaches Einlesen und Schreiben von Dateien Nach 15 Jahren des Wartens gibt es nun auch Methoden, die den Dateiinhalt einlesen oder Strings bzw. ein Byte-Feld schreiben. Listing 5.18: com/tutego/insel/nio2/ListAllLines.java
    package com.tutego.insel.nio2;

    import java.io.IOException;
    import java.net.*;
    import java.nio.charset.Charset;
    import java.nio.file.*;

    public class ListAllLines
    {
      public static void main( String[] args ) throws IOException, URISyntaxException
      {
        URI uri = ListAllLines.class.getResource( "/lyrics.txt" ).toURI();
        Path path = Paths.get( uri );
        System.out.printf( "Datei '%s' mit Länge %d Byte(s) hat folgendes Zeilen:%n",
                           path.getFileName(), Files.size( path ) );
        int lineCnt = 1;
        for ( String line : Files.readAllLines( path, StandardCharsets.UTF_8 ) )
          System.out.println( lineCnt++ + ": " + line );
      }
    }
Abbildung 5.7: UML-Diagramm von Files
Hinweis
Auch wenn es naheliegt, die Files-Methode zum Einlesen mit einem Path-Objekt zu füttern, das ein 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«.
    Path path = Paths.get( new URI( "http://tutego.de/aufgaben/bond.txt" ) );
    String content = new String( Files.readAllBytes( path ),
                                 StandardCharsets.UTF_8 );
    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.
    final class java.nio.file.Files
static long size(Path path) throws IOException static byte[] readAllBytes(Path path) throws IOException static List<String> readAllLines(Path path, Charset cs) throws IOException static Path write(Path path, byte[] bytes, OpenOption... options) throws IOException static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options) throws IOException 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, ... Sollen die Daten nicht direkt aus einer Datei in eine byte-Feld/String-Liste gehen bzw. aus einer byte-Feld/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. ### 5.3.3 Dateien kopieren und verschieben Zum Kopieren und Verschieben (bzw. Umbenennen) von Dateien und Verzeichnissen bietet die Files-Klasse eine copy()- und eine move()-Methode:
    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 5.19: com/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. Vor Java 7 konnte ein renameTo() der File-Klasse zum Verschieben benutzt werden, doch die Semantik war nie klar und die Methode war eigentlich nicht zum Verschieben, sondern nur zum Umbenennen gedacht.
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 Hintergrundthread 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 5.20: java/nio/file/StandardCopyOption.java, StandardCopyOption
    public enum StandardCopyOption implements CopyOption
    {
      REPLACE_EXISTING,
      COPY_ATTRIBUTES,
      ATOMIC_MOVE;
    }
Listing 5.21: java/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:

Tabelle 5.2: Konstanten aus StandardCopyOption

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.
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 mitkopieren. 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 einem anderen Laufwerk. Wenn die Einträge in einem Verzeichnis wirklich auf einem anderen 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.
### 5.3.4 Dateiattribute * Die »alte« File-Klasse wurde immer mehr zum Sammelbecken für alle möglichen Anfragemethoden wie Lesbarkeit, Änderungsdatum usw. Ein Problem dabei ist, dass gewisse Dinge nicht wirklich auf jedem System identisch sind – etwa die Dateirechte. Mit NIO.2 ändert sich das, denn jetzt stehen auch plattformspezifische Dateiattribute zur Verfügung. 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(). Abbildung 5.8: Vererbungsbeziehungen 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 Anfrage-Methoden aufrufen. Ein Beispiel soll die einfachen Attribute ausgeben: Listing 5.22: com/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
Abbildung 5.9: Klassendiagramm 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-Dateisystem sinnvoll sind, etwa isSystem(), und PosixFileAttributes fügt insbesondere Operationen zur Abfrage von Rechten hinzu.

Tabelle 5.3: Operationen auf unterschiedlichen XXXFileAttributes-Schnittstellen

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-Lesen-Bit gesetzt?
boolean isSystem() | System-Datei-Bit gesetzt?
PosixFileAttributes
UserPrincipal owner() | Eigentümer
GroupPrincipal group() | Gruppeneigentümer
Set<PosixFilePermission> permissions() | Rechte der Datei
Bei Posix-Systemem (Windows wird nicht als solches erkannt) lassen sich die Rechte und der Benutzer erfragen: Listing 5.23: com/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 Enum 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 zur Konvertierung von Rechteangaben in der Unix-Notation.
    Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
    System.out.println( PosixFilePermissions.toString( perms ) ); // rwxr-x---
Statt 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() ) );
#### 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 Lese-Methoden, 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 5.24: com/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 Mega-Methode 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 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 zu gehen. 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)

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

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
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 Datei-Zeiten konvertieren. GroupPrincipal repräsentiert einen Benutzernamen bzw. eine Gruppe, und ein UserPrincipalLookupService bietet einen Anfragedienst.
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 getAttribute() in Files, die genau ein Attribut erfragt, und zwei überladene Methoden readAttributes(), die gleich mehrere Attribute auf einmal laden. Dazu ein Beispiel, wie die Methode Files.getAttribute() diverse Attribute erfragt: Listing 5.25: com/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 Attributstring hat ein Präfix dos: oder posix:, der, falls es sich nicht gerade um die Standard-Parameter 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 5.26: com/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-Werte-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 5.27: com/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 Bytefeld 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. ### 5.3.5 Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschen 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 * Zum 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, wo die Verknüpfung angelegt werden soll, und der Parameter target bestimmt den Zielpunkt der Verknüpfung. Die Angabe kann absolut oder relativ sein. Zum Lesen der symbolischen Verknüpfung dient die Files-Methode readSymbolicLink(). Die Methode wie auch createSymbolicLink() ist optional, muss 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. ### 5.3.6 MIME-Typen testen * Die Erkennung von Dateitypen spielt eine wichtige Rolle, etwa dann, wenn für einen Dateityp ein Programm zum Betrachten oder Bearbeiten aufgerufen werden soll. Relativ früh wurde daher der MIME-Typ (Internet Media Type) eingeführt, der Medientypen kennzeichnet. Die wichtigsten Medientypen sind:

Tabelle 5.5: Einige MIME-Typen

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
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. Seit Java 7 bietet NIO.2 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 5.28: com/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 Web-Ressourcen 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 in der Klasse Files gibt dazu Hinweise. ### 5.3.7 Verzeichnislistings (DirectoryStream) und Filter * In der Klasse Files finden sich drei Methoden, um zu einem gegebenen Verzeichnis alle Dateien und Unterverzeichnisse aufzulisten:
    final class java.nio.file.Files
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 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. Listing 5.29: com/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 geschlossen werden muss, da nicht klar ist, ob der Benutzer wirklich alle Dateien abholt oder nach den ersten 10 Einträgen aufhört. Die Schnittstelle DirectoryStream erweitert daher die Schnittstelle Closeable (und die ist AutoCloseable, weshalb unser Beispiel ein try-mit-Ressourcen nutzt), und es ist guter Stil, den DirectoryStream mit close() 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 Methode newDirectoryStream(Path) bietet die einfachste Variante für ein Ablaufen aller Orderinhalte und liefert 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 Filter-Klassen Neben newDirectoryStream(Path) erlauben zwei weitere parametrisierte newDirectoryStream()-Methoden zusätzliche Filter. In der Version newDirectoryStream(Path, String) ist das Filterkriterium durch eine Zeichenkette beschrieben. Oracle nutzt hier die sogenannte Globbing-Syntax, die an reguläre Ausdrücke erinnert. In der API-Dokumentation von FileSystem sind bei der Methode getPathMatcher() einige Beispiele gegeben. Nutzen wir newDirectoryStream(Path, String), um GIF-, JPG- und PNG-Dateien in einem bestimmten Verzeichnis aufzulisten: Listing 5.30: com/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 5.31: com/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 nicht rekursiv. 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 und auch für Comparatoren. Abbildung 5.10: DirectoryStream und Filter als innerer Typ ### 5.3.8 Rekursive Abläufe des Verzeichnisbaums (FileVisitor) * Die Utility-Klasse Files bietet zwei statische Methoden, die, bei einem Startordner beginnend, die Verzeichnisse rekursiv ablaufen.
    final class java.nio.file.Files
static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor) Der erste Parameter bestimmt den Startordner, und der Parameter visitor bestimmt ein Objekt mit Callback-Methoden, die walkFileTree() beim Ablaufen des Verzeichnisbaums aufruft. Ein FileVisitor ist eine Schnittstelle mit folgenden 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 Standard-Implementierung mit folgendem Verhalten:

Tabelle 5.6: Methoden aus SimpleFileVisitor

Methode | Implementierung
preVisitDirectory | return FileVisitResult.CONTINUE;
visitFile | return FileVisitResult.CONTINUE;
visitFileFailed | throw exc;
postVisitDirectory | if (exc != null) throw exc; return FileVisitResult.CONTINUE;
Kommt es also beim Ablaufen zu einem Fehler, führt dies beim SimpleFileVisitor zur IOException, und der Durchlauf bricht ab. Abbildung 5.11: SimpleFileVisitor 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 Falle 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 5.32: com/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. 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, wenn nur ein Dateizugriff zu einem Fehler führt, gleich die Suche im Baum abgebrochen wird. Sollte die Suche bei einem Fehler komplett beendet werden, ist die Rückgabe TERMINATE zu setzen. #### Zyklen erkennen, Verknüpfungen verfolgen, Tiefen angeben Die einfache statische Methode Files.walkFileTree(Path, FileVisitor) läuft den Verzeichnisbaum bis in eine beliebige Tiefe ab.[43]( Nur theoretisch durch Integer.MAX_VAULE beschränkt.) Zudem folgt sie standardmäßig keinen Verknüpfungen. Für einfache Durchsuchungen ist die Methode gut geeignet, aber wer mehr Gestaltungsraum sucht, der greift besser zur zweiten statischen walkFileTree()-Methode in Files: walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor). Das Startverzeichnis und FileVisitor bleiben die gleichen, neu sind die maximale Suchtiefe in Verzeichnisebenen (nicht in der Anzahl der Dateien) und eine Menge von Aufzählungselementen, 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>) selten vor. Hier müssen sich Entwickler zurückerinnern, wie Mengen mit Enums einfach aufgebaut werden können: mit der Klasse EnumSet. 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). ### 5.3.9 Dateisysteme und Dateisystemattribute * Dateisysteme werden durch die Schnittstelle 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 eine URI, bei der das Protokoll ausschlaggebend ist.
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-Archive als Dateisystem unterstützt.
Neue Dateisysteme lassen sich zum Beispiel für die Protokolle http, svn, memory 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() 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. Abbildung 5.12: UML-Diagramme für FileSystems und FileSystem Eine weitere Methode ist getRootDirectories(), die ein Iterable<Path> für die Wurzelverzeichnisse liefert.
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 Java 7 auch einen Dateisystem-Provider für Zip-Archive mitbringt, lassen sich Zip-Archive genauso ablaufen wie »normale« Dateisysteme. Unser nächstes Beispiel soll das jr.jar – Java-Archive sind Zip-Archive – ablaufen. Das sieht so aus: Listing 5.33: com/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
#### 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. Abbildung 5.13: Klassendiagramm von FileStore Listing 5.34: com/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
### 5.3.10 Verzeichnisse im Dateisystem überwachen * Schreibt eine Anwendung etwa Log-Dateien in ein Verzeichnis und soll ein anderes Programm dies erkennen, so gab es bis Java 7 nur die Möglichkeit, ständig das Verzeichnis abzulaufen und nach Änderungen zu suchen. Auch entstanden native Zusatzbibliotheken wie http://jnotify.sourceforge.net/. Seit Java 7 hat sich das geändert, und Oracle hat einen WatchService spendiert. Dazu ein Beispiel, um auf Änderungen im Verzeichnis C:/ zu reagieren: Listing 5.35: com/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
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 Verzeichnissen gebunden, doch Oracle hat WatchService und Watchable in das Paket java.nio.file gelegt. Es bleibt abzuwarten, ob Oracle dieses API auch noch für andere Dinge nutzen wird. Das Watchable meldet sich mit register() am WatchService an. 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 aber 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 der Watch-Service 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 + ausgeschnitten werden, ist noch keine Dateioperation geschehen, denn das Ausschneiden ist nur eine Explorer-Aktion; daher gibt es auch kein Ereignis, sondern nur, wenn die Datei mit + 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.
### Ihr Kommentar Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück | <top> | vor >>
Zum Katalog
Java 7 – Mehr als eine Insel Jetzt bestellen
Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen? Ihre Meinung
Buchempfehlungen
| Java und XML
| Einstieg in Eclipse 3.7
| Android 3
| NetBeans Platform 7
| Java ist auch eine Insel
| Apps entwickeln für Android 4
| Java 7
Shopping
Versandkostenfrei bestellen in Deutschland und Österreich Info

Copyright © Galileo Press 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das 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.

[Galileo Computing]
Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de