Einführung in die Java NIO2 File API

1. Übersicht

In diesem Artikel konzentrieren wir uns auf die neuen E / A-APIs in der Java-Plattform - NIO2 -, um grundlegende Dateimanipulationen durchzuführen .

Datei-APIs in NIO2 stellen einen der wichtigsten neuen Funktionsbereiche der Java-Plattform dar, die mit Java 7 ausgeliefert wurden, insbesondere eine Teilmenge der neuen Dateisystem-API neben Pfad-APIs.

2. Setup

Das Einrichten Ihres Projekts für die Verwendung von Datei-APIs ist nur eine Frage des Imports:

import java.nio.file.*;

Da die Codebeispiele in diesem Artikel wahrscheinlich in verschiedenen Umgebungen ausgeführt werden, erhalten Sie einen Überblick über das Home-Verzeichnis des Benutzers, das für alle Betriebssysteme gültig ist:

private static String HOME = System.getProperty("user.home");

Die Files- Klasse ist einer der primären Einstiegspunkte des Pakets java.nio.file . Diese Klasse bietet eine Vielzahl von APIs zum Lesen, Schreiben und Bearbeiten von Dateien und Verzeichnissen. Die Klassenmethoden Files arbeiten mit Instanzen von Path- Objekten.

3. Überprüfen einer Datei oder eines Verzeichnisses

Wir können eine Pfadinstanz haben, die eine Datei oder ein Verzeichnis im Dateisystem darstellt. Ob die Datei oder das Verzeichnis, auf die bzw. das es verweist, existiert oder nicht, zugänglich ist oder nicht, kann durch einen Dateivorgang bestätigt werden.

Der Einfachheit halber beziehen wir uns bei jeder Verwendung des Begriffs " Datei" sowohl auf Dateien als auch auf Verzeichnisse, sofern nicht ausdrücklich anders angegeben.

Um zu überprüfen, ob eine Datei vorhanden ist, verwenden wir die vorhandene API:

@Test public void givenExistentPath_whenConfirmsFileExists_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.exists(p)); }

Um zu überprüfen, ob eine Datei nicht vorhanden ist, verwenden wir die notExists- API:

@Test public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() { Path p = Paths.get(HOME + "/inexistent_file.txt"); assertTrue(Files.notExists(p)); }

Wir können auch überprüfen, ob eine Datei eine reguläre Datei wie myfile.txt oder nur ein Verzeichnis ist. Wir verwenden die isRegularFile- API:

@Test public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() { Path p = Paths.get(HOME); assertFalse(Files.isRegularFile(p)); }

Es gibt auch statische Methoden, um nach Dateiberechtigungen zu suchen. Um zu überprüfen, ob eine Datei lesbar ist, verwenden wir die isReadable- API:

@Test public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isReadable(p)); }

Um zu überprüfen, ob es beschreibbar ist, verwenden wir die isWritable- API:

@Test public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isWritable(p)); }

Um zu überprüfen, ob es ausführbar ist:

@Test public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isExecutable(p)); }

Wenn wir zwei Pfade haben, können wir überprüfen, ob beide auf dieselbe Datei im zugrunde liegenden Dateisystem verweisen:

@Test public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() { Path p1 = Paths.get(HOME); Path p2 = Paths.get(HOME); assertTrue(Files.isSameFile(p1, p2)); }

4. Dateien erstellen

Die Dateisystem-API bietet einzeilige Operationen zum Erstellen von Dateien. Um eine reguläre Datei zu erstellen, verwenden wir die createFile- API und übergeben ihr ein Path- Objekt, das die Datei darstellt, die wir erstellen möchten.

Bis auf den Dateinamen müssen alle Namenselemente im Pfad vorhanden sein. Andernfalls wird eine IOException angezeigt:

@Test public void givenFilePath_whenCreatesNewFile_thenCorrect() { String fileName = "myfile_" + UUID.randomUUID().toString() + ".txt"; Path p = Paths.get(HOME + "/" + fileName); assertFalse(Files.exists(p)); Files.createFile(p); assertTrue(Files.exists(p)); }

Wenn wir im obigen Test den Pfad zum ersten Mal überprüfen , ist er nicht vorhanden. Nach der Operation createFile wird festgestellt, dass er vorhanden ist.

Um ein Verzeichnis zu erstellen, verwenden wir die createDirectory- API:

@Test public void givenDirPath_whenCreatesNewDir_thenCorrect() { String dirName = "myDir_" + UUID.randomUUID().toString(); Path p = Paths.get(HOME + "/" + dirName); assertFalse(Files.exists(p)); Files.createDirectory(p); assertTrue(Files.exists(p)); assertFalse(Files.isRegularFile(p)); assertTrue(Files.isDirectory(p)); }

Diese Operation erfordert, dass alle Namenselemente im Pfad vorhanden sind. Andernfalls erhalten wir auch eine IOException :

@Test(expected = NoSuchFileException.class) public void givenDirPath_whenFailsToCreateRecursively_thenCorrect() { String dirName = "myDir_" + UUID.randomUUID().toString() + "/subdir"; Path p = Paths.get(HOME + "/" + dirName); assertFalse(Files.exists(p)); Files.createDirectory(p); }

Wenn wir jedoch mit einem einzigen Aufruf eine Hierarchie von Verzeichnissen erstellen möchten , verwenden wir die Methode createDirectories . Im Gegensatz zur vorherigen Operation wird beim Auffinden fehlender Namenselemente im Pfad keine IOException ausgelöst , sondern diese werden rekursiv erstellt, bis zum letzten Element:

@Test public void givenDirPath_whenCreatesRecursively_thenCorrect() { Path dir = Paths.get( HOME + "/myDir_" + UUID.randomUUID().toString()); Path subdir = dir.resolve("subdir"); assertFalse(Files.exists(dir)); assertFalse(Files.exists(subdir)); Files.createDirectories(subdir); assertTrue(Files.exists(dir)); assertTrue(Files.exists(subdir)); }

5. Temporäre Dateien erstellen

Viele Anwendungen erstellen während der Ausführung eine Spur temporärer Dateien im Dateisystem. Infolgedessen verfügen die meisten Dateisysteme über ein dediziertes Verzeichnis zum Speichern temporärer Dateien, die von solchen Anwendungen generiert werden.

Die neue Dateisystem-API bietet zu diesem Zweck spezielle Vorgänge. Die createTempFile- API führt diesen Vorgang aus. Es werden ein Pfadobjekt, ein Dateipräfix und ein Dateisuffix benötigt:

@Test public void givenFilePath_whenCreatesTempFile_thenCorrect() { String prefix = "log_"; String suffix = ".txt"; Path p = Paths.get(HOME + "/"); Files.createTempFile(p, prefix, suffix); assertTrue(Files.exists(p)); }

Diese Parameter reichen für Anforderungen aus, die diesen Vorgang erfordern. Wenn Sie jedoch bestimmte Attribute der Datei angeben müssen, gibt es einen vierten Parameter für variable Argumente.

Der obige Test erstellt eine temporäre Datei im HOME- Verzeichnis, in der die angegebenen Präfix- bzw. Suffix-Zeichenfolgen vorab anhängig sind und angehängt werden. Am Ende erhalten wir einen Dateinamen wie log_8821081429012075286.txt . Die lange numerische Zeichenfolge wird vom System generiert.

Wenn wir jedoch kein Präfix und kein Suffix angeben, enthält der Dateiname nur die lange numerische Zeichenfolge und eine Standarderweiterung .tmp :

@Test public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() { Path p = Paths.get(HOME + "/"); Files.createTempFile(p, null, null); assertTrue(Files.exists(p)); }

Die obige Operation erstellt eine Datei mit einem Namen wie 8600179353689423985.tmp .

Wenn wir schließlich weder Pfad, Präfix noch Suffix angeben, verwendet die Operation durchgehend Standardeinstellungen. Der Standardspeicherort der erstellten Datei ist das vom Dateisystem bereitgestellte temporäre Dateiverzeichnis:

@Test public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() { Path p = Files.createTempFile(null, null); assertTrue(Files.exists(p)); }

Unter Windows wird standardmäßig C: \ Benutzer \ Benutzer \ AppData \ Local \ Temp \ 6100927974988978748.tmp verwendet .

Alle oben genannten Vorgänge können angepasst werden, um Verzeichnisse anstelle von regulären Dateien zu erstellen, indem createTempDirectory anstelle von createTempFile verwendet wird .

6. Löschen einer Datei

Um eine Datei zu löschen, verwenden wir die Lösch- API. Aus Gründen der Übersichtlichkeit stellt der folgende Test zunächst sicher, dass die Datei noch nicht vorhanden ist, erstellt sie dann und bestätigt, dass sie jetzt vorhanden ist, löscht sie schließlich und bestätigt, dass sie nicht mehr vorhanden ist:

@Test public void givenPath_whenDeletes_thenCorrect() { Path p = Paths.get(HOME + "/fileToDelete.txt"); assertFalse(Files.exists(p)); Files.createFile(p); assertTrue(Files.exists(p)); Files.delete(p); assertFalse(Files.exists(p)); }

Wenn jedoch keine Datei im Dateisystem vorhanden ist, schlägt der Löschvorgang mit einer IOException fehl :

@Test(expected = NoSuchFileException.class) public void givenInexistentFile_whenDeleteFails_thenCorrect() { Path p = Paths.get(HOME + "/inexistentFile.txt"); assertFalse(Files.exists(p)); Files.delete(p); }

Wir können dieses Szenario vermeiden, indem wir deleteIfExists verwenden, die stillschweigend fehlschlagen, falls die Datei nicht vorhanden ist. Dies ist wichtig, wenn mehrere Threads diesen Vorgang ausführen und wir keine Fehlermeldung wünschen, nur weil ein Thread den Vorgang früher ausgeführt hat als der aktuelle Thread, der fehlgeschlagen ist:

@Test public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() { Path p = Paths.get(HOME + "/inexistentFile.txt"); assertFalse(Files.exists(p)); Files.deleteIfExists(p); }

Wenn Sie sich mit Verzeichnissen und nicht mit regulären Dateien befassen, sollten Sie beachten, dass der Löschvorgang standardmäßig nicht rekursiv funktioniert. Wenn ein Verzeichnis nicht leer ist, schlägt dies mit einer IOException fehl :

@Test(expected = DirectoryNotEmptyException.class) public void givenPath_whenFailsToDeleteNonEmptyDir_thenCorrect() { Path dir = Paths.get( HOME + "/emptyDir" + UUID.randomUUID().toString()); Files.createDirectory(dir); assertTrue(Files.exists(dir)); Path file = dir.resolve("file.txt"); Files.createFile(file); Files.delete(dir); assertTrue(Files.exists(dir)); }

7. Dateien kopieren

Sie können eine Datei oder ein Verzeichnis mithilfe der Kopier- API kopieren :

@Test public void givenFilePath_whenCopiesToNewLocation_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); assertTrue(Files.exists(file1)); assertFalse(Files.exists(file2)); Files.copy(file1, file2); assertTrue(Files.exists(file2)); }

Die Kopie schlägt fehl, wenn die Zieldatei vorhanden ist, sofern nicht die Option REPLACE_EXISTING angegeben ist:

@Test(expected = FileAlreadyExistsException.class) public void givenPath_whenCopyFailsDueToExistingFile_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); Files.createFile(file2); assertTrue(Files.exists(file1)); assertTrue(Files.exists(file2)); Files.copy(file1, file2); Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING); }

Beim Kopieren von Verzeichnissen wird der Inhalt jedoch nicht rekursiv kopiert. Dies bedeutet , dass wenn / baeldung enthält /articles.db und /authors.db Dateien, Kopieren / baeldung an einen neuen Standort wird ein leeres Verzeichnis erstellen.

8. Dateien verschieben

Sie können eine Datei oder ein Verzeichnis mithilfe der Verschiebungs- API verschieben. Es ähnelt in den meisten Punkten dem Kopiervorgang . Wenn der Kopiervorgang einem Kopier- und Einfügevorgang in GUI-basierten Systemen entspricht, erfolgt das Verschieben analog zu einem Ausschneide- und Einfügevorgang :

@Test public void givenFilePath_whenMovesToNewLocation_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); assertTrue(Files.exists(file1)); assertFalse(Files.exists(file2)); Files.move(file1, file2); assertTrue(Files.exists(file2)); assertFalse(Files.exists(file1)); }

Der Verschiebevorgang schlägt fehl, wenn die Zieldatei vorhanden ist, es sei denn, die Option REPLACE_EXISTING wird wie beim Kopiervorgang angegeben :

@Test(expected = FileAlreadyExistsException.class) public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); Files.createFile(file2); assertTrue(Files.exists(file1)); assertTrue(Files.exists(file2)); Files.move(file1, file2); Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); assertTrue(Files.exists(file2)); assertFalse(Files.exists(file1)); }

9. Fazit

In diesem Artikel haben wir uns mit Datei-APIs in der neuen Dateisystem-API (NIO2) vertraut gemacht, die als Teil von Java 7 ausgeliefert wurde, und die meisten wichtigen Dateivorgänge in Aktion gesehen.

Die in diesem Artikel verwendeten Codebeispiele finden Sie im Github-Projekt des Artikels.