Einführung in die Java NIO2-Datei-API

1. Überblick

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

Datei-APIs in NIO2 bilden einen der wichtigsten neuen Funktionsbereiche der Java-Plattform, die im Lieferumfang von Java 7 enthalten ist, insbesondere eine Teilmenge der neuen Dateisystem-API neben Pfad-APIs.

2. Konfiguration

Um Ihr Projekt für die Verwendung von Datei-APIs einzurichten, müssen Sie nur diesen Import durchführen:

import java.nio.file.** ;

Da die Codebeispiele in diesem Artikel wahrscheinlich in verschiedenen Umgebungen ausgeführt werden, erhalten wir einen Handle für 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 Files -Klassenmethoden arbeiten mit Instanzen von Path -Objekten.

3. Überprüfen einer Datei oder eines Verzeichnisses

Wir können eine Path -Instanz haben, die eine Datei oder ein Verzeichnis im Dateisystem darstellt. Ob diese Datei oder dieses Verzeichnis vorhanden ist oder nicht, auf die zugegriffen werden kann oder nicht, kann durch einen Dateivorgang bestätigt werden

Der Einfachheit halber beziehen wir uns, wenn wir den Begriff file verwenden, auf Dateien und Verzeichnisse, sofern nicht ausdrücklich etwas anderes angegeben ist.

Um zu prüfen, ob eine Datei existiert, verwenden wir die exists -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 prüfen, ob es sich bei einer Datei um eine reguläre Datei wie myfile.txt handelt oder nur um ein Verzeichnis handelt. 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 prüfen, ob es schreibbar 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 zeigen:

@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 ein Path -Objekt an diese, das die zu erstellende Datei darstellt.

Alle Namenselemente im Pfad müssen vorhanden sein, mit Ausnahme des Dateinamens. Andernfalls erhalten Sie eine IOException:

@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 ersten Test den Pfad zuerst prüfen, ist er nicht vorhanden. Nach der createFile -Operation wird er als vorhanden erkannt.

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));
}

Für diesen Vorgang müssen alle Namenselemente im Pfad vorhanden sein. Andernfalls erhalten Sie 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 Sie jedoch mit einem einzigen Aufruf eine Hierarchie von Verzeichnissen erstellen möchten, verwenden Sie die Methode createDirectories . Anders als bei der vorherigen Operation löst ein IOException keine fehlenden Namenselemente im Pfad aus, sondern erzeugt diese rekursiv und führt 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 eine Spur temporärer Dateien im Dateisystem, während sie ausgeführt werden. Daher verfügen die meisten Dateisysteme über ein eigenes Verzeichnis, in dem temporäre Dateien gespeichert werden, die von solchen Anwendungen generiert werden.

Die neue Dateisystem-API bietet spezielle Vorgänge für diesen Zweck.

Die createTempFile -API führt diesen Vorgang aus. Es benötigt ein Pfadobjekt, ein Dateipräfix und ein Dateisuffix:

@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 diese Operation benötigen. Wenn Sie jedoch bestimmte Attribute der Datei angeben müssen, gibt es einen vierten Argumentparameterparameter.

Der obige Test erstellt eine temporäre Datei im Verzeichnis HOME , die die angegebenen Präfix- und Suffix-Zeichenfolgen anhängt und anfügt. Am Ende wird ein Dateiname wie log 8821081429012075286.txt__ angezeigt. Die lange numerische Zeichenfolge wird vom System generiert.

Wenn Sie 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 weder Pfad, Präfix noch Suffix angeben, verwendet der Vorgang die Standardwerte. Der Standardspeicherort der erstellten Datei ist das vom temporären Dateiverzeichnis bereitgestellte Dateisystem:

@Test
public void givenNoFilePath__whenCreatesTempFileInTempDir__thenCorrect() {
    Path p = Files.createTempFile(null, null);

    assertTrue(Files.exists(p));
}

Unter Windows wird dies standardmäßig auf etwas wie C: \ Users \ AppData \ Local \ Temp \ 6100927974988978748.tmp festgelegt.

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

6. Datei löschen

Um eine Datei zu löschen, verwenden wir die delete -API. Zur Verdeutlichung stellt der folgende Test zunächst sicher, dass die Datei noch nicht vorhanden ist, erstellt sie und bestätigt, dass sie jetzt existiert, löscht sie schließlich und bestätigt, dass sie nicht mehr existiert:

@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 Sie deleteIfExists verwenden, das unbemerkt fehlschlägt, falls die Datei nicht vorhanden ist. Dies ist wichtig, wenn mehrere Threads diesen Vorgang ausführen und wir keine Fehlermeldung erhalten möchten, 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);
}

Beim Umgang mit Verzeichnissen und nicht mit regulären Dateien sollten wir daran denken, dass der Löschvorgang standardmäßig nicht rekursiv funktioniert. Wenn also ein Verzeichnis nicht leer ist, schlägt es 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 copy -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 die Option REPLACE EXISTING__ nicht 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 werden die Inhalte jedoch nicht rekursiv kopiert. Das heißt, wenn /baeldung /articles.db - und /hors.db -Dateien enthält, wird beim Kopieren von /baeldung an einen neuen Speicherort ein leeres Verzeichnis erstellt.

8. Dateien verschieben

Sie können eine Datei oder ein Verzeichnis mithilfe der move -API verschieben. Es ist in vielerlei Hinsicht der copy -Operation ähnlich. Wenn die Kopieroperation in GUI-basierten Systemen einer copy- und paste -Operation entspricht, ist move analog einer cut- und paste -Operation:

@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));
}

Die Operation move schlägt fehl, wenn die Zieldatei vorhanden ist, es sei denn, die Option REPLACE EXISTING wird genau wie bei der Operation copy__ 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), die als Teil von Java 7 ausgeliefert wurde, vertraut gemacht und die meisten wichtigen Dateivorgänge in Aktion gesehen.

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