Dateien und Verzeichnisse unter Java

Table of Contents

Dateioperationen mit java.io werden in Java immer, direkt oder indirekt, durch ein Objekt des Typs java.io.File abgebildet. Dabei kann File aber nicht selbst aus Dateien lesen, oder in sie schreiben, dazu benötigt man einen Reader oder Writer (für Textdateien) resp. einen InputStream oder OutputStream (für Binärdateien).

Dateien und Pfade

Ein File wird immer aus einer Pfadangabe, entweder absolut oder relativ erzeugt. Absolut geht von einem Wurzelverzeichnis aus, zum Beispiel C:\ unter Windows, oder / unter Linux. Ein relativer Pfad bezieht sich dagegen auf das aktuelle Verzeichnis des Benutzers, normalerweise von dort aus woher das Programm aufgerufen wurde. Wenn die Datei unter dem angegebenen Pfad nicht existiert, wird auch keine entsprechende Datei unter dem definierten Pfad angelegt:

File windowsDatei = new File("C:\\home\\user\\text.txt");
File linuxDatei = new File("/home/user/text.txt");

Pfade werden also je nach OS unterschiedlich angegeben. Für Windows Pfade als Konstante sind effektiv zwei Doppel Backslashes zu verwenden! Solange die Pfadeingabe vom Benutzer selbst kommt, macht das keine Probleme. Wenn aber aus dem Programm heraus Dateipfade erzeugt werden müssen, dann müssen Sie auf diese Details acht geben. Das richtige Zeichen zum trennen von Verzeichnissen in einer Pfadangabe findet sich in der Konstanten File.separator. Damit lässt sich einen Pfad OS unabhängig erstellen: 

File datei = new File(File.separator + "home" 
    + File.separator + "user"
    + File.separator + "text.txt");

Unter Unix-basierten Systemen funktioniert dieser Code. Unter Windows bleibt das Problem mit dem Laufwerksbuchstaben. Dazu kann unabhängig vom OS, alle Wurzelverzeichnisse aufgelistet werden. Dazu kennt File die statische Methode listRoots. So lässt sich ein OS unabhängiger Code schreiben:

public File waehleWurzel(){
    File[] wurzeln = File.listRoots();
    if (wurzeln.length == 1){
        return wurzeln[0];
    } else {
        System. out.println("Bitte wählen Sie eine Wurzel");
        for (int i = 0; i < wurzeln.length; i++){
            System.out.println(i + ": " + wurzeln[i]);
        }
        int index = liesZahl();
        return wurzeln[index];
    }
}

Doch das reicht immer noch nicht ganz, denn unter Windows kann es hier mehrere Einträge geben, je nachdem wieviele Laufwerke vorhanden sind (C:\, D:\ ...). In diesem Fall wird der Benutzer geben, ein Laufwerk auszuwählen. Anschliessend kann ein neues File-Objekt relativ zur ausgewählten Wurzel erzeugt werden, indem dies dem Konstruktor angegeben wird:

File wurzel = waehleWurzel();
File datei = new File(wurzel, "home"
 + File.separator + "user"
 + File.separator + "text.txt");

Ob eine Datei überhaupt existiert kann mit der Methode exists überprüft werden. Da ein File lediglich die objektorientierte Repräsentation eines Pfades ist, kann man Files erzeugen, ohne das diese Dateien bereits existieren. Falls keine Datei existiert kann eine neue Datei mit createNewFile oder mit mkdir ein Verzeichnis an der vom Pfad angegebenen Stelle angelegt werden. File liefert weitere Informationen über Dateien:

MethodeFunktion
isFile()Prüft, ob es sich bei der angegebenen File-Objekt um eine Datei handelt, (oder einen Ordner)
isDirectory()Prüft, ob es sich bei der angegebenen File-Objekt um einen Ordner handelt, (oder eine Datei)
canRead()Prüft, ob der Benutzer Leserechte hat
canWrite()Prüft, ob der Benutzer Schreibrechte hat
canExectue()Prüft, ob der Benutzer Ausführungsrechte hat
getName()Liefert den Namen der Datei, ohne vorangehende Pfadangabe
getParent()
getParentFile()
Liefert das übergeordnete Verzeichnis, entweder als String mit getParent oder als Objekt mit getParentFile
lastModified()Liefert das letzte Änderungsdatum der Datei als long
length()Liefert die Grösse der Datei in Byte als long
deleteeine Datei löschen
renameToeine Datei umbenennen

java.io.File stellt jedoch keine Methoden zur Verfügung für das Kopieren oder verschieben von Dateien. Dazu gibt es aber seit Java 7 eine Hilfsklasse aus Files.

Dateioperationen aus "Files"

Die Klasse Files ist eine Sammlung von Hilfsmethoden für alles, was mit Dateien zu tun hat. Diese Klasse ist aber nicht im java.io-Package enthalten, sondern nur im java.nio.files. Damit ist es die einzige Klasse aus der Non-Blocking-I/O-API, die man auch beim alltäglichen Umgang mit Dateien regelmässig benutzt.

Da die Hilfsklasse zu der API java.nio.file.Path gehört und nicht zur java.io.File müssen bei jeder Operation die Parameter von File nach Path und die Rückgabewerte, falls Dateien zurückgegeben werden, wieder von Path nach File konvertiert werden. Dabei lassen sich nicht alle Path-Objekte nach File konvertieren, nur solche welche aus einer Operation auf einem File resultieren. Damit sind auch Kopier- und Verschiebeoperationen möglich. Die Verwendung dieser Methode macht mehr Sinn, als eine Eigenentwicklung in Java, da dies nicht nur praktischer ist, sondern auch effizienter die JDL Systemaufrufe verwenden kann.

//File nach Path konvertieren
Path quellPath = quelle.toPath();
Path zielPath = ziel.toPath();
//ENTWEDER Datei kopieren
Path ergebnisPath = Files.copy(quellPath, zielPath);
//ODER Datei verschieben
Path ergebnisPath = Files.move(quellPath, zielPath);
//Ergebnis - eigentlich wieder das Ziel - nach File konvertieren
File ergebnis = ergebnisPath.toFile();

Verzeichnisse

Um den Inhalt von Verzeichnissen zu ermitteln, gibt es die überladene Methode listFiles die zur Files Klasse gehört. Ohne Parameter gibt sie alle im Verzeichnis enthaltenen Dateien zurück. Wenn man nur an bestimmten Dateien interessiert ist, dann sollte man entweder einen FileFilter oder einen FilenameFilter an listFiles übergeben. Die beiden Filterklassen unterscheiden sich nur darin, dass FileFilter das File-Objekt der gefundenen Datei zur Prüfung erhält, FilenameFilter den Dateinamen als String und das aktuelle Verzeichnis. Beide Filter sind funktionale Interfaces und können deshalb auch als Lambdas angegeben werden.

//Alle Dateien auflisten
File[] alleDateien = verzeichnis.listFiles();
//Alle Dateien mit der Endung .txt auflisten 
File[] textDateien = verzeichnis.listFiles((parent, name) -> 
 name.endsWith(".txt"));
//Alle Unterverzeichnisse auflisten
File[] unterverzeichnisse = verzeichnis.listFiles(file -> 
 file.isDirectory());

Auch zum Auflisten des Verzeichnisinhalts hat die Klasse Files Hilfsmethoden. list gibt dabei den Inhalt eines Verzeichnisses als einen Stream von Path-Objekten zurück. walk, listet nicht nur den Inhalt des übergebenen Verzeichnisses auf, sondern auch aller Unterverzeichnisse, ist also rekursiv.

Files.walk(quelle.toPath()).forEach(System.out::println);

Optional kann die Tiefe der rekursiven Auflistung limitiert werden bis zu einer bestimmten Tiefe. walk(quelle, 1) enthält nur den Inhalt des Verzeichnisses selbst, tut also dasselbe wie list. walk(quelle, 2) enthält den Inhalt der Verzeichnisses und seiner direkten Unterverzeichnisse usw.

Weiter kann mit Files.find in einem Verzeichnis und dessen Unterverzeichnisse nach Dateien gesucht werden, die bestimmten Vorgaben entsprechen. Leider ist auch hier dass java.io und java.nio nicht aus einem Guss. So muss man also die Suchkriterien nicht als FileFilter angeben, sondern als BiPredicate, das als Parameter des Path-Objekt der Datei und ein Objekt vom Typ BasicFileAttributes erhält, in dem sich Informationen wie Dateigrösse und letzte Zugriffszeit finden. Das Beispiel zeigt, wie man Dateien, die grösser als 500MB sind, auflisten kann. Der grosse Nachteil von walk und find ist, dass wenn der Zugriff auf ein Verzeichnis nicht möglich ist, brechen sie mit einer Fehlermeldung ab. Es gibt keine Funktion, diesen Methoden beizubringen, bei unlesbaren Verzeichnissen einfach den Fehler zu ignorieren und weiter zu suchen. Deswegen wird häufig dennoch auf File.listFiles zurückgegriffen.