Eine Anleitung zu NIO2 Asynchronous File Channel

Eine Anleitung zum asynchronen NIO2-Dateikanal

1. Überblick

In diesem Artikel werden wir eine der wichtigsten zusätzlichen APIs der neuen E / A (NIO2) in den asynchronen Java 7-Dateikanal-APIs untersuchen.

Wenn Sie mit APIs für asynchrone Kanäle im Allgemeinen noch nicht vertraut sind, finden Sie auf dieser Site einen Einführungsartikel, den Sie lesen können, indem Siethis link folgen, bevor Sie fortfahren.

Sie können auch mehr über NIO.2file operations undpath operations lesen. Wenn Sie diese verstehen, ist es viel einfacher, diesem Artikel zu folgen.

Um die asynchronen NIO2-Dateikanäle in unseren Projekten zu verwenden, müssen wir das Paketjava.nio.channelsimportieren, da es alle erforderlichen Klassen bündelt:

import java.nio.channels.*;

2. DieAsynchronousFileChannel

In diesem Abschnitt erfahren Sie, wie Sie die Hauptklasse verwenden, mit der wir asynchrone Operationen für Dateien ausführen können, dieAsynchronousFileChannel-Klasse. Um eine Instanz davon zu erstellen, rufen wir die statischeopen-Methode auf:

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

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

Alle Aufzählungswerte stammen aus StandardOpenOption.

Der erste Parameter für die geöffnete API ist einPath-Objekt, das den Dateispeicherort darstellt. Um mehr über Pfadoperationen in NIO2 zu erfahren, folgen Siethis link. Die anderen Parameter bilden einen Satz mit Optionen, die dem zurückgegebenen Dateikanal zur Verfügung stehen sollen.

Der von uns erstellte asynchrone Dateikanal kann verwendet werden, um alle bekannten Operationen an einer Datei auszuführen. Um nur eine Teilmenge der Operationen auszuführen, würden wir nur für diese Optionen angeben. Zum Beispiel, um nur zu lesen:

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

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

3. Lesen aus einer Datei

Wie bei allen asynchronen Vorgängen in NIO2 kann das Lesen des Inhalts einer Datei auf zwei Arten erfolgen. Verwenden SieFuture undCompletionHandler. In jedem Fall verwenden wir dieread API des zurückgegebenen Kanals.

Erstellen Sie im Ordner "Testressourcen" von maven oder im Quellverzeichnis, wenn Sie maven nicht verwenden, eine Datei mit dem Namenfile.txt, wobei nur der Textexample.com am Anfang steht. Wir werden nun zeigen, wie dieser Inhalt gelesen wird.

3.1. Der zukünftige Ansatz

Zunächst sehen wir, wie eine Datei mit der KlasseFutureasynchron gelesen wird:

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

Im obigen Code verwenden wir nach dem Erstellen eines Dateikanals dieread-API, dieByteBuffer benötigt, um den aus dem Kanal gelesenen Inhalt als ersten Parameter zu speichern.

Der zweite Parameter ist ein langer Wert, der die Position in der Datei angibt, von der aus mit dem Lesen begonnen werden soll.

Die Methode gibt sofort zurück, ob die Datei gelesen wurde oder nicht.

Als nächstes können wir jeden anderen Code ausführen, während der Vorgang im Hintergrund fortgesetzt wird. Wenn wir mit der Ausführung von anderem Code fertig sind, können wir dieget()-API aufrufen, die sofort zurückgegeben wird, wenn die Operation bereits abgeschlossen wurde, als wir anderen Code ausgeführt haben, oder sie blockiert, bis die Operation abgeschlossen ist.

Unsere Behauptung belegt in der Tat, dass der Inhalt der Datei gelesen wurde.

Wenn wir den Positionsparameter im API-Aufruf vonreadvon Null auf etwas anderes geändert hätten, würden wir auch den Effekt sehen. Beispielsweise ist das siebte Zeichen in der Zeichenfolgeexample.comg. Wenn Sie also den Positionsparameter auf 7 ändern, enthält der Puffer die Zeichenfolgeg.com.

3.2. DerCompletionHandler Ansatz

Als nächstes sehen wir, wie der Inhalt einer Datei mit einerCompletionHandler-Instanz gelesen wird:

@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) {

        }
    });
}

Im obigen Code verwenden wir die zweite Variante derread API. Es dauert immer noch einByteBuffer und die Startposition derread-Operation als ersten bzw. zweiten Parameter. Der dritte Parameter ist die Instanz vonCompletionHandler.

Der erste generische Typ des Beendigungshandlers ist der Rückgabetyp der Operation, in diesem Fall eine Ganzzahl, die die Anzahl der gelesenen Bytes darstellt.

Der zweite ist der Typ des Anhangs. Wir haben uns entschieden, den Puffer so anzuhängen, dass wir nach Abschluss vonread den Inhalt der Datei in der Rückruf-API voncompletedverwenden können.

Semantisch gesehen ist dies kein wirklich gültiger Komponententest, da wir innerhalb der Rückrufmethode voncompletedkeine Aussage treffen können. Wir tun dies jedoch aus Gründen der Konsistenz und weil wir möchten, dass unser Code socopy-paste-run-able wie möglich ist.

4. In eine Datei schreiben

Mit Java NIO2 können wir auch Schreibvorgänge für eine Datei ausführen. Genau wie bei anderen Operationen können wir auf zwei Arten in eine Datei schreiben. Verwenden SieFuture undCompletionHandler. In jedem Fall verwenden wir diewrite API des zurückgegebenen Kanals.

Das Erstellen einesAsynchronousFileChannel zum Schreiben in eine Datei kann folgendermaßen erfolgen:

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

4.1. Besondere Überlegungen

Beachten Sie die an die API vonopenübergebene Option. Wir können auch eine weitere OptionStandardOpenOption.CREATE hinzufügen, wenn die durchpath dargestellte Datei erstellt werden soll, falls sie noch nicht vorhanden ist. Eine weitere häufige Option istStandardOpenOption.APPEND, bei der vorhandene Inhalte in der Datei nicht überschrieben werden.

Wir werden die folgende Zeile verwenden, um unseren Dateikanal zu Testzwecken zu erstellen:

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

Auf diese Weise geben wir einen beliebigen Pfad an und stellen sicher, dass die Datei erstellt wird. Nach Beendigung des Tests wird die erstellte Datei gelöscht. Um sicherzustellen, dass die erstellten Dateien nach dem Beenden des Tests nicht gelöscht werden, können Sie die letzte Option entfernen.

Um Assertions ausführen zu können, müssen wir den Dateiinhalt nach dem Schreiben soweit wie möglich lesen. Lassen Sie uns die Logik zum Lesen in einer separaten Methode ausblenden, um Redundanz zu vermeiden:

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. DerFuture Ansatz

So schreiben Sie asynchron mit der KlasseFuturein eine Datei:

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

Lassen Sie uns untersuchen, was im obigen Code passiert. Wir erstellen einen zufälligen Dateinamen und verwenden ihn, um einPath-Objekt zu erhalten. Wir verwenden diesen Pfad, um einen asynchronen Dateikanal mit den zuvor genannten Optionen zu öffnen.

Wir legen dann den Inhalt, den wir in die Datei schreiben möchten, in einen Puffer und führen diewrite aus. Wir verwenden unsere Hilfsmethode, um den Inhalt der Datei zu lesen und tatsächlich zu bestätigen, dass es das ist, was wir erwarten.

4.3. DerCompletionHandler Ansatz

Wir können auch den Completion-Handler verwenden, damit wir nicht warten müssen, bis der Vorgang in einer while-Schleife abgeschlossen ist:

@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) {

        }
    });
}

Wenn wir diesmal die Schreib-API aufrufen, ist das einzig Neue ein dritter Parameter, bei dem wir eine anonyme innere Klasse vom TypCompletionHandler übergeben.

Wenn der Vorgang abgeschlossen ist, ruft die Klasse die abgeschlossene Methode auf, mit der wir definieren können, was passieren soll.

5. Fazit

In diesem Artikel haben wir einige der wichtigsten Funktionen der Asynchronous File Channel-APIs von Java NIO2 untersucht.

Um alle Codefragmente und den vollständigen Quellcode für diesen Artikel abzurufen, besuchen Sie dieGithub project.