Introduction à l’API de fichier Java NIO2

1. Vue d’ensemble

Dans cet article, nous allons nous concentrer sur les nouvelles API d’E/S de la plate-forme Java - NIO2 - pour la manipulation de fichier de base .

Les API de fichier dans NIO2 constituent l’un des nouveaux principaux domaines fonctionnels de la plate-forme Java fournie avec Java 7, en particulier un sous-ensemble de la nouvelle API de système de fichiers aux côtés des API de chemin.

2. Installer

Configurer votre projet pour utiliser les API de fichier consiste simplement à effectuer cette importation:

import java.nio.file.** ;

Dans la mesure où les exemples de code de cet article fonctionneront probablement dans des environnements différents, obtenons un identificateur du répertoire de base de l’utilisateur, qui sera valide sous tous les systèmes d’exploitation:

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

La classe Files est l’un des principaux points d’entrée du package java.nio.file . Cette classe offre un riche ensemble d’API pour la lecture, l’écriture et la manipulation de fichiers et de répertoires. Les méthodes de la classe Files fonctionnent sur les instances d’objets Path .

3. Vérification d’un fichier ou d’un répertoire

Nous pouvons avoir une instance Path représentant un fichier ou un répertoire sur le système de fichiers. Que le fichier ou le répertoire indiqué existe ou non, qu’il soit accessible ou non, peut être confirmé par une opération sur un fichier.

Par souci de simplicité, chaque fois que nous utilisons le terme file , nous ferons référence aux fichiers et aux répertoires, sauf indication contraire explicite.

Pour vérifier si un fichier existe, nous utilisons l’API exists :

@Test
public void givenExistentPath__whenConfirmsFileExists__thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.exists(p));
}

Pour vérifier qu’un fichier n’existe pas, nous utilisons l’API notExists :

@Test
public void givenNonexistentPath__whenConfirmsFileNotExists__thenCorrect() {
    Path p = Paths.get(HOME + "/inexistent__file.txt");

    assertTrue(Files.notExists(p));
}

Nous pouvons également vérifier si un fichier est un fichier normal comme myfile.txt ou est simplement un répertoire, nous utilisons l’API isRegularFile :

@Test
public void givenDirPath__whenConfirmsNotRegularFile__thenCorrect() {
    Path p = Paths.get(HOME);

    assertFalse(Files.isRegularFile(p));
}

Il existe également des méthodes statiques pour vérifier les autorisations de fichiers. Pour vérifier si un fichier est lisible, nous utilisons l’API isReadable :

@Test
public void givenExistentDirPath__whenConfirmsReadable__thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.isReadable(p));
}

Pour vérifier si elle est accessible en écriture, nous utilisons l’API isWritable :

@Test
public void givenExistentDirPath__whenConfirmsWritable__thenCorrect() {
    Path p = Paths.get(HOME);

    assertTrue(Files.isWritable(p));
}

De même, pour vérifier s’il est exécutable:

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

Lorsque nous avons deux chemins, nous pouvons vérifier s’ils pointent tous deux vers le même fichier sur le système de fichiers sous-jacent:

@Test
public void givenSameFilePaths__whenConfirmsIsSame__thenCorrect() {
    Path p1 = Paths.get(HOME);
    Path p2 = Paths.get(HOME);

    assertTrue(Files.isSameFile(p1, p2));
}

4. Création de fichiers

L’API du système de fichiers fournit des opérations d’une seule ligne pour la création de fichiers.

Pour créer un fichier normal, nous utilisons l’API createFile et lui transmettons un objet Path représentant le fichier que nous voulons créer.

Tous les éléments de nom dans le chemin doivent exister, à part le nom du fichier, sinon nous obtiendrons une exception__IO:

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

Dans le test ci-dessus, lorsque nous vérifions le chemin pour la première fois, il est inexistant, puis après l’opération createFile , il s’avère être existant.

Pour créer un répertoire, nous utilisons l’API createDirectory :

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

Cette opération nécessite que tous les éléments de nom du chemin existent, sinon nous obtenons également une 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);
}

Cependant, si nous souhaitons créer une hiérarchie de répertoires avec un seul appel, nous utilisons la méthode createDirectories . Contrairement à l’opération précédente, lorsqu’il rencontre un élément de nom manquant dans le chemin, il ne lance pas une IOException , il les crée de manière récursive menant au dernier élément:

@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. Création de fichiers temporaires

De nombreuses applications créent une piste de fichiers temporaires dans le système de fichiers au cours de leur exécution. En conséquence, la plupart des systèmes de fichiers disposent d’un répertoire dédié pour stocker les fichiers temporaires générés par ces applications.

La nouvelle API de système de fichiers fournit des opérations spécifiques à cette fin.

L’API createTempFile effectue cette opération. Il faut un objet chemin, un préfixe de fichier et un suffixe de fichier:

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

Ces paramètres sont suffisants pour les besoins nécessitant cette opération. Toutefois, si vous devez spécifier des attributs spécifiques du fichier, il existe un quatrième paramètre d’arguments variables.

Le test ci-dessus crée un fichier temporaire dans le répertoire HOME , en attente et ajoute les chaînes de préfixe et de suffixe fournies. Nous allons nous retrouver avec un nom de fichier comme log 8821081429012075286.txt__. La longue chaîne numérique est générée par le système.

Cependant, si nous ne fournissons pas de préfixe ni de suffixe, le nom du fichier n’inclura que la longue chaîne numérique et une extension par défaut .tmp :

@Test
public void givenPath__whenCreatesTempFileWithDefaults__thenCorrect() {
    Path p = Paths.get(HOME + "/");

    Files.createTempFile(p, null, null);

    assertTrue(Files.exists(p));
}

L’opération ci-dessus crée un fichier portant un nom comme 8600179353689423985.tmp .

Enfin, si nous ne fournissons ni chemin, préfixe ni suffixe, l’opération utilisera les valeurs par défaut. L’emplacement par défaut du fichier créé sera le répertoire de fichiers temporaires fourni par le système de fichiers:

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

    assertTrue(Files.exists(p));
}

Sous Windows, la valeur par défaut sera C: \ Utilisateurs \ Utilisateur \ AppData \ Local \ Temp \ 6100927974988978748.tmp .

Toutes les opérations ci-dessus peuvent être adaptées pour créer des répertoires plutôt que des fichiers ordinaires en utilisant createTempDirectory au lieu de createTempFile .

6. Supprimer un fichier

Pour supprimer un fichier, nous utilisons l’API delete . Par souci de clarté, le test suivant vérifie d’abord que le fichier n’existe pas, puis le crée, confirme qu’il existe maintenant, le supprime enfin et confirme qu’il n’existe plus:

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

Cependant, si un fichier n’existe pas dans le système de fichiers, l’opération de suppression échouera avec une exception IOException :

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

    Files.delete(p);
}

Nous pouvons éviter ce scénario en utilisant deleteIfExists qui échouent en mode silencieux au cas où le fichier n’existe pas. Ceci est important lorsque plusieurs threads effectuent cette opération et que nous ne voulons pas de message d’échec simplement parce qu’un thread a effectué l’opération avant le thread actuel qui a échoué:

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

    Files.deleteIfExists(p);
}

Lorsqu’il s’agit de répertoires et non de fichiers normaux, nous devons nous rappeler que l’opération de suppression ne fonctionne pas de manière récursive par défaut. Donc, si un répertoire n’est pas vide, il échouera avec une IOException :

@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. Copier des fichiers

Vous pouvez copier un fichier ou un répertoire à l’aide de l’API copy :

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

La copie échoue si le fichier cible existe sauf si l’option REPLACE EXISTING__ est spécifiée:

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

Cependant, lors de la copie de répertoires, le contenu n’est pas copié de manière récursive. Cela signifie que si /baeldung contient les fichiers /articles.db et /authors.db , copier /baeldung dans un nouvel emplacement créera un répertoire vide.

8. Déplacement de fichiers

Vous pouvez déplacer un fichier ou un répertoire à l’aide de l’API move . Il ressemble dans la plupart des cas à l’opération copy . Si l’opération de copie est analogue à une opération copy and paste dans des systèmes basés sur une interface graphique, alors move est analogue à une opération cut and paste :

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

L’opération move échoue si le fichier cible existe sauf si l’option REPLACE EXISTING est spécifiée exactement comme nous l’avons fait avec l’opération copy__:

@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. Conclusion

Dans cet article, nous avons découvert les API de fichier dans la nouvelle API de système de fichiers (NIO2) livrée avec Java 7 et intégrant la plupart des opérations de fichier importantes.

Les exemples de code utilisés dans cet article se trouvent dans le projet Github