Guia do Java FileChannel

Guia do Java FileChannel

1. Visão geral

Neste tutorial rápido, veremos a classeFileChannel fornecida na bibliotecaJava NIO. Discutiremoshow to read and write data using FileChannel and ByteBuffer.

Também exploraremos as vantagens de usarFileChannele alguns de seus outros recursos de manipulação de arquivo.

2. Vantagens deFileChannel

As vantagens deFileChannel incluem:

  • Leitura e gravação em uma posição específica em um arquivo

  • Carregar uma seção de um arquivo diretamente na memória, o que pode ser mais eficiente

  • Podemos transferir dados de arquivo de um canal para outro em uma taxa mais rápida

  • Podemos bloquear uma seção de um arquivo para restringir o acesso por outros threads

  • Para evitar a perda de dados, podemos forçar a gravação de atualizações em um arquivo imediatamente para armazenamento

3. Lendo comFileChannel

FileChannel tem desempenho mais rápido do que E / S padrão quando lemos um arquivo grande.

Devemos notar que embora façam parte deJava NIO,FileChannel operations are blockinge não tenham um modo sem bloqueio.

3.1. Lendo um arquivo usandoFileChannel

Vamos entender como ler um arquivo usandoFileChannel em um arquivo que contém:

Hello world

Este teste lê o arquivo e verifica se foi lido ok:

@Test
public void givenFile_whenReadWithFileChannelUsingRandomAccessFile_thenCorrect()
  throws IOException {
    try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
        FileChannel channel = reader.getChannel();
        ByteArrayOutputStream out = new ByteArrayOutputStream()) {

        int bufferSize = 1024;
        if (bufferSize > channel.size()) {
           bufferSize = (int) channel.size();
        }
        ByteBuffer buff = ByteBuffer.allocate(bufferSize);

        while (channel.read(buff) > 0) {
            out.write(buff.array(), 0, buff.position());
            buff.clear();
        }

     String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8);

     assertEquals("Hello world", fileContent);
    }
}

Aqui, lemos bytes do arquivo usandoFileChannel,RandomAccessFile eByteBuffer.

Devemos também observar quemultiple concurrent threads can useFileChannels safely.  No entanto, apenas um thread por vez é permitido uma operação que envolve atualizar a posição de um canal ou alterar seu tamanho de arquivo. Isso bloqueia outros threads que tentam uma operação semelhante até a operação anterior ser concluída.

No entanto, operações que fornecem posições explícitas de canal podem ser executadas simultaneamente sem serem bloqueadas.

3.2. Abrindo umFileChannel

Para ler um arquivo usandoFileChannel, devemos abri-lo.

Vamos ver como abrir umFileChannel usingRandomAccessFile:

RandomAccessFile reader = new RandomAccessFile(file, "r");
FileChannel channel = reader.getChannel();

Mode ‘r' indicates that the channel is ‘open for reading' only. Devemos observar que fechar uma lavagemRandomAccessFile também fecha o canal associado.

A seguir, veremos a abertura deFileChannel para ler um arquivo usandoFileInputStream:

FileInputStream fin= new FileInputStream(file);
FileChannel channel = fin.getChannel();

Da mesma forma, fechar umFileInputStream também fecha o canal associado a ele.

3.3. Lendo dados de umFileChannel

Para ler os dados, podemos usar um dos métodosread.

Vamos ver como ler uma sequência de bytes. Usaremos umByteBuffer para armazenar os dados:

ByteBuffer buff = ByteBuffer.allocate(1024);
int noOfBytesRead = channel.read(buff);
String fileContent = new String(buff.array(), StandardCharsets.UTF_8);

assertEquals("Hello world", fileContent);

A seguir, veremos como ler uma sequência de bytes, começando na posição do arquivo:

ByteBuffer buff = ByteBuffer.allocate(1024);
int noOfBytesRead = channel.read(buff, 5);
String fileContent = new String(buff.array(), StandardCharsets.UTF_8);
assertEquals("world", fileContent);

We should note the need for a Charset to decode a byte array into String.

Especificamos oCharset com o qual os bytes foram codificados originalmente. Sem ele,, podemos acabar com o texto truncado. Em particular, codificações multibyte comoUTF-8eUTF-16 podem não ser capazes de decodificar uma seção arbitrária do arquivo, pois alguns dos caracteres multibyte podem estar incompletos.

4. Escrevendo comFileChannel

4.1. Escrevendo em um arquivo usandoFileChannel

Vamos explorar como escrever usandoFileChannel:

@Test
public void whenWriteWithFileChannelUsingRandomAccessFile_thenCorrect()
  throws IOException {
    String file = "src/test/resources/test_write_using_filechannel.txt";
    try (RandomAccessFile writer = new RandomAccessFile(file, "rw");
        FileChannel channel = writer.getChannel()){
        ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));

        channel.write(buff);

     // verify
     RandomAccessFile reader = new RandomAccessFile(file, "r");
     assertEquals("Hello world", reader.readLine());
     reader.close();
    }
}

4.2. Abrindo umFileChannel

Para escrever em um arquivo usandoFileChannel, devemos abri-lo.

Vamos ver como abrir umFileChannel usingRandomAccessFile:

RandomAccessFile writer = new RandomAccessFile(file, "rw");
FileChannel channel = writer.getChannel();

O modo 'rw' indica que o canal está 'aberto para leitura e escrita'.

Vejamos também como abrir umFileChannel usandoFileOutputStream:

FileOutputStream fout = new FileOutputStream(file);
FileChannel channel = fout.getChannel();

4.3. Gravando dados comFileChannel

Para escrever dados comFileChannel, podemos usar um dos métodoswrite.

Vamos ver como escrever uma sequência de bytes, usando umByteBuffer para armazenar os dados:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));
channel.write(buff);

A seguir, veremos como escrever uma sequência de bytes, começando na posição do arquivo:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));
channel.write(buff, 5);

5. Posição atual

FileChannel nos permite obter e alterar a posição em que estamos lendo ou escrevendo.

Vamos ver como obter a posição atual:

long originalPosition = channel.position();

A seguir, vamos ver como definir a posição:

channel.position(5);
assertEquals(originalPosition + 5, channel.position());

6. Obter o tamanho de um arquivo

Vamos ver como usar o métodoFileChannel.size para obter o tamanho de um arquivo em bytes:

@Test
public void whenGetFileSize_thenCorrect()
  throws IOException {
    RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
    FileChannel channel = reader.getChannel();

    // the original file size is 11 bytes.
    assertEquals(11, channel.size());

    channel.close();
    reader.close();
}

7. Truncar um arquivo

Vamos entender como usar o métodoFileChannel.truncate para truncar um arquivo para um determinado tamanho em bytes:

@Test
public void whenTruncateFile_thenCorrect()
  throws IOException {
    String input = "this is a test input";

    FileOutputStream fout = new FileOutputStream("src/test/resources/test_truncate.txt");
    FileChannel channel = fout.getChannel();

    ByteBuffer buff = ByteBuffer.wrap(input.getBytes());
    channel.write(buff);
    buff.flip();

    channel = channel.truncate(5);
    assertEquals(5, channel.size());

    fout.close();
    channel.close();
}

8. Forçar atualização de arquivo no armazenamento

Um sistema operacional pode armazenar em cache alterações no arquivo por motivos de desempenho, e os dados podem ser perdidos se o sistema travar. Para forçar o conteúdo do arquivo e metadados a gravar no disco continuamente, podemos usar o métodoforce:

channel.force(true);

Este método é garantido apenas quando o arquivo reside em um dispositivo local.

9. Carregar uma seção de um arquivo na memória

Vamos ver como carregar uma seção de um arquivo na memória usandoFileChannel.map. UsamosFileChannel.MapMode.READ_ONLY para abrir o arquivo no modo somente leitura:

@Test
public void givenFile_whenReadAFileSectionIntoMemoryWithFileChannel_thenCorrect()
  throws IOException {
    try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
        FileChannel channel = reader.getChannel();
        ByteArrayOutputStream out = new ByteArrayOutputStream()) {

        MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 6, 5);

        if(buff.hasRemaining()) {
          byte[] data = new byte[buff.remaining()];
          buff.get(data);
          assertEquals("world", new String(data, StandardCharsets.UTF_8));
        }
    }
}

Da mesma forma, podemos usarFileChannel.MapMode.READ_WRITE para abrir o arquivo no modo de leitura e gravação.

Também podemos usar o modo FileChannel.MapMode.PRIVATE* *, onde as alterações não se aplicam ao arquivo original.

10. Bloquear uma seção de um arquivo

Vamos entender como bloquear uma seção de um arquivo para evitar o acesso simultâneo de uma seção usando o métodoFileChannel.tryLock:

@Test
public void givenFile_whenWriteAFileUsingLockAFileSectionWithFileChannel_thenCorrect()
  throws IOException {
    try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw");
        FileChannel channel = reader.getChannel();
        FileLock fileLock = channel.tryLock(6, 5, Boolean.FALSE )){

        //do other operations...

        assertNotNull(fileLock);
    }
}

O métodotryLock tenta obter um bloqueio na seção do arquivo. Se a seção do arquivo solicitada já estiver bloqueada por outro encadeamento, ele lançará uma exceçãoOverlappingFileLockException. Este método também usa um parâmetroboolean para solicitar um bloqueio compartilhado ou exclusivo.

Devemos observar que alguns sistemas operacionais podem não permitir um bloqueio compartilhado, assumindo como padrão um bloqueio exclusivo.

11. Fechando aFileChannel

Finalmente, quando terminarmos de usar umFileChannel, devemos fechá-lo. Em nossos exemplos, usamostry-with-resources.

Se necessário, podemos fecharFileChannel diretamente com o métodoclose:

channel.close();

12. Conclusão

Neste tutorial, vimoshow to use FileChannel to read and write files. Além disso, exploramos como ler e alterar o tamanho do arquivo e seu local atual de leitura / gravação e vimos como usarFileChannels em aplicativos simultâneos ou de dados críticos.

Como sempre, o código-fonte dos exemplos está disponívelover on GitHub.