Um guia para o canal de arquivo assíncrono NIO2

Um guia para o canal de arquivo assíncrono NIO2

1. Visão geral

Neste artigo, exploraremos uma das principais APIs adicionais da nova E / S (NIO2) no Java 7, APIs de canal de arquivo assíncrono.

Se você é novo em APIs de canal assíncrono em geral, temos um artigo introdutório neste site que você pode ler seguindothis link antes de prosseguir.

Você pode ler mais sobre NIO.2file operationsepath operations também - entendê-los tornará este artigo muito mais fácil de seguir.

Para usar os canais de arquivo assíncronos NIO2 em nossos projetos, temos que importar o pacotejava.nio.channels, pois ele agrupa todas as classes necessárias:

import java.nio.channels.*;

2. OAsynchronousFileChannel

Nesta seção, exploraremos como usar a classe principal que nos permite realizar operações assíncronas em arquivos, a classeAsynchronousFileChannel. Para criar uma instância dele, chamamos o método estáticoopen:

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

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

Todos os valores enum vêm de StandardOpenOption.

O primeiro parâmetro para a API aberta é um objetoPath que representa a localização do arquivo. Para ler mais sobre as operações de caminho em NIO2, sigathis link. Os outros parâmetros compõem um conjunto que especifica opções que devem estar disponíveis para o canal de arquivo retornado.

O canal de arquivo assíncrono que criamos pode ser usado para executar todas as operações conhecidas em um arquivo. Para executar apenas um subconjunto das operações, especificaríamos opções apenas para essas. Por exemplo, para ler apenas:

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

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

3. Lendo de um arquivo

Assim como com todas as operações assíncronas no NIO2, a leitura do conteúdo de um arquivo pode ser feita de duas maneiras. UsandoFuturee usandoCompletionHandler. Em cada caso, usamos a APIread do canal retornado.

Dentro da pasta de recursos de teste do maven ou no diretório de origem, se não estiver usando o maven, vamos criar um arquivo chamadofile.txt com apenas o textoexample.com no início. Agora demonstraremos como ler este conteúdo.

3.1. A abordagem do futuro

Primeiro, veremos como ler um arquivo de forma assíncrona usando a 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");
}

No código acima, após criar um canal de arquivo, usamos a APIread - que usa umByteBuffer para armazenar o conteúdo lido do canal como seu primeiro parâmetro.

O segundo parâmetro é um longo, indicando a posição no arquivo a partir da qual iniciar a leitura.

O método retorna imediatamente se o arquivo foi lido ou não.

Em seguida, podemos executar qualquer outro código à medida que a operação continua em segundo plano. Quando terminarmos de executar outro código, podemos chamar a APIget() que retorna imediatamente se a operação já foi concluída enquanto estávamos executando outro código, ou então bloqueia até que a operação seja concluída.

Nossa afirmação de fato prova que o conteúdo do arquivo foi lido.

Se tivéssemos alterado o parâmetro de posição na chamada da APIread de zero para outra coisa, veríamos o efeito também. Por exemplo, o sétimo caractere na stringexample.com ég. Portanto, alterar o parâmetro de posição para 7 faria com que o buffer contivesse a stringg.com.

3.2. A abordagemCompletionHandler

A seguir, veremos como ler o conteúdo de um arquivo usando uma instânciaCompletionHandler:

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

        }
    });
}

No código acima, usamos a segunda variante da APIread. Ainda levaByteBuffere a posição inicial da operaçãoread como o primeiro e o segundo parâmetros, respectivamente. O terceiro parâmetro é a instânciaCompletionHandler.

O primeiro tipo genérico do manipulador de conclusão é o tipo de retorno da operação, nesse caso, um número inteiro que representa o número de bytes lidos.

O segundo é o tipo de anexo. Optamos por anexar o buffer de forma que, quandoread for concluído, possamos usar o conteúdo do arquivo dentro da API de retorno de chamadacompleted.

Semanticamente falando, este não é realmente um teste de unidade válido, pois não podemos fazer uma asserção dentro do método de retorno de chamadacompleted. No entanto, fazemos isso por uma questão de consistência e porque queremos que nosso código seja o maiscopy-paste-run-able possível.

4. Gravando em um arquivo

O Java NIO2 também nos permite executar operações de gravação em um arquivo. Assim como fizemos com outras operações, podemos gravar em um arquivo de duas maneiras. UsandoFuturee usandoCompletionHandler. Em cada caso, usamos a APIwrite do canal retornado.

Criar umAsynchronousFileChannel para gravar em um arquivo pode ser feito assim:

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

4.1. Considerações Especiais

Observe a opção passada para a APIopen. Também podemos adicionar outra opçãoStandardOpenOption.CREATE se quisermos que o arquivo representado porpath seja criado, caso ainda não exista. Outra opção comum éStandardOpenOption.APPEND, que não sobrescreve o conteúdo existente no arquivo.

Usaremos a seguinte linha para criar nosso canal de arquivos para fins de teste:

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

Dessa forma, forneceremos qualquer caminho arbitrário e garantiremos que o arquivo seja criado. Após a conclusão do teste, o arquivo criado será excluído. Para garantir que os arquivos criados não sejam excluídos após a saída do teste, você pode remover a última opção.

Para executar asserções, precisaremos ler o conteúdo do arquivo sempre que possível depois de gravá-las. Vamos esconder a lógica de leitura em um método separado para evitar redundância:

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. A abordagemFuture

Para gravar em um arquivo de forma assíncrona usando a 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);
}

Vamos inspecionar o que está acontecendo no código acima. Criamos um nome de arquivo aleatório e o usamos para obter um objetoPath. Usamos esse caminho para abrir um canal de arquivo assíncrono com as opções mencionadas anteriormente.

Em seguida, colocamos o conteúdo que queremos gravar no arquivo em um buffer e executamos owrite. Usamos nosso método auxiliar para ler o conteúdo do arquivo e, de fato, confirmar que é o que esperamos.

4.3. A abordagemCompletionHandler

Também podemos usar o manipulador de conclusão para que não tenhamos que esperar a operação ser concluída em um loop 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) {

        }
    });
}

Quando chamamos a API de gravação desta vez, a única coisa nova é um terceiro parâmetro onde passamos uma classe interna anônima do tipoCompletionHandler.

Quando a operação é concluída, a classe chama seu método concluído dentro do qual podemos definir o que deve acontecer.

5. Conclusão

Neste artigo, exploramos alguns dos recursos mais importantes das APIs de canal de arquivo assíncrono do Java NIO2.

Para obter todos os trechos de código e o código-fonte completo para este artigo, você pode visitar oGithub project.