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 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

Buch bestellen
Ihre Meinung?

Spacer
Java 7 - Mehr als eine Insel von Christian Ullenboom
Das Handbuch zu den Java SE-Bibliotheken
Buch: Java 7 - Mehr als eine Insel

Java 7 - Mehr als eine Insel
Rheinwerk Computing
1433 S., 2012, geb.
49,90 Euro, ISBN 978-3-8362-1507-7
Pfeil 5 Dateien, Verzeichnisse und Dateizugriffe
Pfeil 5.1 Datei und Verzeichnis
Pfeil 5.1.1 Dateien und Verzeichnisse mit der Klasse File
Pfeil 5.1.2 Verzeichnis oder Datei? Existiert es?
Pfeil 5.1.3 Verzeichnis- und Dateieigenschaften/-attribute
Pfeil 5.1.4 Umbenennen und Verzeichnisse anlegen
Pfeil 5.1.5 Verzeichnisse auflisten und Dateien filtern
Pfeil 5.1.6 Dateien berühren, neue Dateien anlegen, temporäre Dateien
Pfeil 5.1.7 Dateien und Verzeichnisse löschen
Pfeil 5.1.8 Verzeichnisse nach Dateien iterativ durchsuchen *
Pfeil 5.1.9 Wurzelverzeichnis, Laufwerksnamen, Plattenspeicher *
Pfeil 5.1.10 URL-, URI- und Path-Objekte aus einem File-Objekt ableiten *
Pfeil 5.1.11 Mit Locking Dateien sperren *
Pfeil 5.1.12 Sicherheitsprüfung *
Pfeil 5.1.13 Zugriff auf SMB-Server mit jCIFS *
Pfeil 5.2 Dateien mit wahlfreiem Zugriff
Pfeil 5.2.1 Ein RandomAccessFile zum Lesen und Schreiben öffnen
Pfeil 5.2.2 Aus dem RandomAccessFile lesen
Pfeil 5.2.3 Schreiben mit RandomAccessFile
Pfeil 5.2.4 Die Länge des RandomAccessFile
Pfeil 5.2.5 Hin und her in der Datei
Pfeil 5.3 Dateisysteme unter NIO.2
Pfeil 5.3.1 FileSystem und Path
Pfeil 5.3.2 Die Utility-Klasse Files
Pfeil 5.3.3 Dateien kopieren und verschieben
Pfeil 5.3.4 Dateiattribute *
Pfeil 5.3.5 Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschen
Pfeil 5.3.6 MIME-Typen testen *
Pfeil 5.3.7 Verzeichnislistings (DirectoryStream) und Filter *
Pfeil 5.3.8 Rekursive Abläufe des Verzeichnisbaums (FileVisitor) *
Pfeil 5.3.9 Dateisysteme und Dateisystemattribute *
Pfeil 5.3.10 Verzeichnisse im Dateisystem überwachen *
Pfeil 5.4 Wahlfreier Zugriff mit SeekableByteChannel und ByteBuffer *
Pfeil 5.4.1 SeekableByteChannel
Pfeil 5.4.2 ByteBuffer
Pfeil 5.4.3 Beispiel mit Path + SeekableByteChannel + ByteBuffer
Pfeil 5.4.4 FileChannel
Pfeil 5.5 Zum Weiterlesen

Rheinwerk Computing - Zum Seitenanfang

5.3 Dateisysteme unter NIO.2Zur nächsten Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.1 FileSystem und PathZur nächsten ÜberschriftZur vorigen Überschrift

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

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

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

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.2 Die Utility-Klasse FilesZur nächsten ÜberschriftZur vorigen Überschrift

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

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" ) ); Fehler
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.


Rheinwerk Computing - Zum Seitenanfang

5.3.3 Dateien kopieren und verschiebenZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.4 Dateiattribute *Zur nächsten ÜberschriftZur vorigen Überschrift

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

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

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.5 Neue Dateien, Verzeichnisse, symbolische Verknüpfungen anlegen und löschenZur nächsten ÜberschriftZur vorigen Ü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 *

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.6 MIME-Typen testen *Zur nächsten ÜberschriftZur vorigen Ü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. 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.


Rheinwerk Computing - Zum Seitenanfang

5.3.7 Verzeichnislistings (DirectoryStream) und Filter *Zur nächsten ÜberschriftZur vorigen Überschrift

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

Abbildung 5.10: DirectoryStream und Filter als innerer Typ


Rheinwerk Computing - Zum Seitenanfang

5.3.8 Rekursive Abläufe des Verzeichnisbaums (FileVisitor) *Zur nächsten ÜberschriftZur vorigen Überschrift

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

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).


Rheinwerk Computing - Zum Seitenanfang

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

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

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

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

Rheinwerk Computing - Zum Seitenanfang

5.3.10 Verzeichnisse im Dateisystem überwachen *Zur vorigen Überschrift

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 Strg+X 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 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.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
  Zum Katalog
Neuauflage: Java SE 8 Standard-Bibliothek
Neuauflage: Java SE 8 Standard-Bibliothek
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Einstieg in Eclipse






 Einstieg in Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 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.


Nutzungsbestimmungen | Datenschutz | Impressum

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