Guide sur le canal de fichier asynchrone NIO2

Guide sur le canal de fichier asynchrone NIO2

1. Vue d'ensemble

Dans cet article, nous allons explorer l'une des principales API supplémentaires des nouvelles API d'E / S (NIO2) de Java 7, canaux de fichiers asynchrones.

Si vous êtes nouveau dans les API de canaux asynchrones en général, nous avons un article d'introduction sur ce site que vous pouvez lire en suivantthis link avant de continuer.

Vous pouvez également en savoir plus sur NIO.2file operations etpath operations - comprendre cela rendra cet article beaucoup plus facile à suivre.

Pour utiliser les canaux de fichiers asynchrones NIO2 dans nos projets, nous devons importer le packagejava.nio.channels car il regroupe toutes les classes requises:

import java.nio.channels.*;

2. LesAsynchronousFileChannel

Dans cette section, nous allons explorer comment utiliser la classe principale qui nous permet d'effectuer des opérations asynchrones sur des fichiers, la classeAsynchronousFileChannel. Pour en créer une instance, nous appelons la méthode statiqueopen:

Path filePath = Paths.get("/path/to/file");

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
  filePath, READ, WRITE, CREATE, DELETE_ON_CLOSE);

Toutes les valeurs d'énumération proviennent de StandardOpenOption.

Le premier paramètre de l'API ouverte est un objetPath représentant l'emplacement du fichier. Pour en savoir plus sur les opérations de chemin dans NIO2, suivezthis link. Les autres paramètres constituent un ensemble spécifiant les options devant être disponibles pour le canal de fichier renvoyé.

Le canal de fichier asynchrone que nous avons créé peut être utilisé pour effectuer toutes les opérations connues sur un fichier. Pour effectuer uniquement un sous-ensemble des opérations, nous spécifierions des options pour celles-ci uniquement. Par exemple, à lire uniquement:

Path filePath = Paths.get("/path/to/file");

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
  filePath, StandardOpenOption.READ);

3. Lire à partir d'un fichier

Comme pour toutes les opérations asynchrones dans NIO2, la lecture du contenu d’un fichier peut être effectuée de deux manières. En utilisantFuture et en utilisantCompletionHandler. Dans chaque cas, nous utilisons l'APIread du canal retourné.

Dans le dossier des ressources de test de maven ou dans le répertoire source si vous n'utilisez pas maven, créons un fichier appeléfile.txt avec uniquement le texteexample.com au début. Nous allons maintenant montrer comment lire ce contenu.

3.1. L'approche future

Tout d'abord, nous verrons comment lire un fichier de manière asynchrone en utilisant la classeFuture:

@Test
public void givenFilePath_whenReadsContentWithFuture_thenCorrect() {
    Path path = Paths.get(
      URI.create(
        this.getClass().getResource("/file.txt").toString()));
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      path, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    Future operation = fileChannel.read(buffer, 0);

    // run other code as operation continues in background
    operation.get();

    String fileContent = new String(buffer.array()).trim();
    buffer.clear();

    assertEquals(fileContent, "example.com");
}

Dans le code ci-dessus, après avoir créé un canal de fichier, nous utilisons l'APIread - qui prend unByteBuffer pour stocker le contenu lu depuis le canal comme premier paramètre.

Le deuxième paramètre est un long indiquant la position dans le fichier à partir de laquelle commencer la lecture.

La méthode retourne immédiatement si le fichier a été lu ou non.

Ensuite, nous pouvons exécuter tout autre code à mesure que l'opération se poursuit en arrière-plan. Lorsque nous avons fini d'exécuter un autre code, nous pouvons appeler l'APIget() qui retourne tout de suite si l'opération est déjà terminée alors que nous exécutions un autre code, ou bien elle se bloque jusqu'à la fin de l'opération.

Notre affirmation prouve en effet que le contenu du fichier a été lu.

Si nous avions changé le paramètre de position dans l'appel d'APIread de zéro à autre chose, nous verrions également l'effet. Par exemple, le septième caractère de la chaîneexample.com estg. Ainsi, changer le paramètre de position sur 7 ferait en sorte que le tampon contienne la chaîneg.com.

3.2. L'approcheCompletionHandler

Ensuite, nous verrons comment lire le contenu d'un fichier à l'aide d'une instanceCompletionHandler:

@Test
public void
  givenPath_whenReadsContentWithCompletionHandler_thenCorrect() {

    Path path = Paths.get(
      URI.create( this.getClass().getResource("/file.txt").toString()));
    AsynchronousFileChannel fileChannel
      = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    fileChannel.read(
      buffer, 0, buffer, new CompletionHandler() {

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // result is number of bytes read
            // attachment is the buffer containing content
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

Dans le code ci-dessus, nous utilisons la deuxième variante de l'APIread. Il prend toujours unByteBuffer et la position de départ de l'opérationread comme premier et second paramètres respectivement. Le troisième paramètre est l'instanceCompletionHandler.

Le premier type générique du gestionnaire d'achèvement est le type de retour de l'opération, dans ce cas un Integer représentant le nombre d'octets lus.

La seconde est le type de la pièce jointe. Nous avons choisi d'attacher le tampon de telle sorte que lorsque leread est terminé, nous pouvons utiliser le contenu du fichier dans l'API de rappel decompleted.

Sémantiquement parlant, ce n'est pas vraiment un test unitaire valide car nous ne pouvons pas faire une assertion dans la méthode de rappel decompleted. Cependant, nous le faisons par souci de cohérence et parce que nous voulons que notre code soit aussicopy-paste-run-able que possible.

4. Écriture dans un fichier

Java NIO2 nous permet également d'effectuer des opérations d'écriture sur un fichier. Comme nous l'avons fait avec d'autres opérations, nous pouvons écrire dans un fichier de deux manières. En utilisantFuture et en utilisantCompletionHandler. Dans chaque cas, nous utilisons l'APIwrite du canal retourné.

Créer unAsynchronousFileChannel pour écrire dans un fichier peut être fait comme ceci:

AsynchronousFileChannel fileChannel
  = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

4.1. Considérations particulières

Notez l'option transmise à l'APIopen. Nous pouvons également ajouter une autre optionStandardOpenOption.CREATE si nous voulons que le fichier représenté par unpath soit créé au cas où il n'existerait pas déjà. Une autre option courante estStandardOpenOption.APPEND qui n'écrase pas le contenu existant dans le fichier.

Nous allons utiliser la ligne suivante pour créer notre chaîne de fichiers à des fins de test:

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
  path, WRITE, CREATE, DELETE_ON_CLOSE);

De cette façon, nous fournirons tout chemin arbitraire et nous assurerons que le fichier sera créé. Une fois le test terminé, le fichier créé sera supprimé. Pour vous assurer que les fichiers créés ne sont pas supprimés une fois le test terminé, vous pouvez supprimer la dernière option.

Pour exécuter des assertions, nous devrons lire le contenu du fichier si possible après leur avoir écrit. Masquons la logique de lecture dans une méthode distincte pour éviter la redondance:

public static String readContent(Path file) {
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      file, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    Future operation = fileChannel.read(buffer, 0);

    // run other code as operation continues in background
    operation.get();

    String fileContent = new String(buffer.array()).trim();
    buffer.clear();
    return fileContent;
}

4.2. L'approcheFuture

Pour écrire dans un fichier de manière asynchrone à l'aide de la classeFuture:

@Test
public void
  givenPathAndContent_whenWritesToFileWithFuture_thenCorrect() {

    String fileName = UUID.randomUUID().toString();
    Path path = Paths.get(fileName);
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      path, WRITE, CREATE, DELETE_ON_CLOSE);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    buffer.put("hello world".getBytes());
    buffer.flip();

    Future operation = fileChannel.write(buffer, 0);
    buffer.clear();

    //run other code as operation continues in background
    operation.get();

    String content = readContent(path);
    assertEquals("hello world", content);
}

Examinons ce qui se passe dans le code ci-dessus. Nous créons un nom de fichier aléatoire et l'utilisons pour obtenir un objetPath. Nous utilisons ce chemin pour ouvrir un canal de fichier asynchrone avec les options mentionnées précédemment.

Nous plaçons ensuite le contenu que nous voulons écrire dans le fichier dans un tampon et effectuons leswrite. Nous utilisons notre méthode d'assistance pour lire le contenu du fichier et nous confirmons que c'est ce que nous attendons.

4.3. L'approcheCompletionHandler

Nous pouvons également utiliser le gestionnaire de complétion pour ne pas avoir à attendre que l'opération se termine dans une boucle while:

@Test
public void
  givenPathAndContent_whenWritesToFileWithHandler_thenCorrect() {

    String fileName = UUID.randomUUID().toString();
    Path path = Paths.get(fileName);
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      path, WRITE, CREATE, DELETE_ON_CLOSE);

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("hello world".getBytes());
    buffer.flip();

    fileChannel.write(
      buffer, 0, buffer, new CompletionHandler() {

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // result is number of bytes written
            // attachment is the buffer
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

Lorsque nous appelons l'API d'écriture cette fois, la seule nouveauté est un troisième paramètre où nous passons une classe interne anonyme de typeCompletionHandler.

Une fois l'opération terminée, la classe appelle la méthode terminée dans laquelle nous pouvons définir ce qui doit se passer.

5. Conclusion

Dans cet article, nous avons exploré certaines des fonctionnalités les plus importantes des API de canaux de fichiers asynchrones de Java NIO2.

Pour obtenir tous les extraits de code et le code source complet de cet article, vous pouvez visiter lesGithub project.