CodeGym /Cursos /JAVA 25 SELF /Modos de trabalho com arquivos: leitura, gravação, anexaç...

Modos de trabalho com arquivos: leitura, gravação, anexação

JAVA 25 SELF
Nível 35 , Lição 3
Disponível

1. Gravação com sobrescrita

Quando você trabalha com arquivos, é importante entender que “gravar em um arquivo” — nem sempre significa a mesma coisa. Às vezes, é preciso totalmente sobrescrever o arquivo (por exemplo, quando você salva um novo relatório), e às vezes — adicionar novas informações ao final de um arquivo já existente (por exemplo, manter o log de eventos do aplicativo). Em alguns casos, é preciso apenas ler o conteúdo do arquivo, sem alterá-lo.

Em Java (especialmente no pacote moderno java.nio.file) o modo de trabalho com o arquivo é definido por um conjunto de opções especiais que você passa para os métodos de gravação, por exemplo, para Files.write(). Essas opções permitem indicar explicitamente se você quer sobrescrever o arquivo ou adicionar algo ao final.

Como isso funciona

Quando você chama Files.write(path, data), o Java, por padrão, cria um novo arquivo ou sobrescreve o já existente. Todo o conteúdo antigo do arquivo será descartado, e no lugar dele ficarão apenas os novos dados.

Esse comportamento padrão é uma espécie de “reinicialização forçada” do arquivo. Se antes o arquivo tinha 1000 linhas e você gravou apenas uma — tudo o que havia antes desaparecerá sem deixar rastro.

Exemplo: gravando linhas em um arquivo

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class OverwriteFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> lines = List.of("Olá, mundo!", "Este é um novo registro no arquivo.");

        // Gravamos as linhas no arquivo (o conteúdo antigo será removido)
        Files.write(path, lines);

        System.out.println("Arquivo sobrescrito com sucesso!");
    }
}

Após executar esse código, no arquivo myfile.txt restarão apenas as duas linhas da lista lines. Tudo o que havia antes desaparecerá (sem quaisquer “arrependimentos” por parte do Java).

2. Anexação (append): adicionando dados ao final do arquivo

Em algumas tarefas (por exemplo, manutenção de diário de eventos, logging, acumulação de dados) é necessário não apagar o conteúdo antigo do arquivo, mas adicionar novas linhas ao final. Na API antiga, para isso muitas vezes se usava FileWriter com o flag append = true. Na API moderna, tudo é feito de forma mais simples e explícita.

Como isso é feito

Tudo o que você precisa é passar a opção StandardOpenOption.APPEND para o método Files.write():

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class AppendFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> moreLines = List.of("Adicionamos mais uma linha.", "E mais uma!");

        // Adicionamos as linhas ao final do arquivo (o conteúdo antigo é preservado)
        Files.write(path, moreLines, StandardOpenOption.APPEND);

        System.out.println("Linhas adicionadas ao final do arquivo!");
    }
}

Ponto importante: se o arquivo não existir, ao tentar anexar ocorrerá um erro — o Java não criará o arquivo automaticamente no modo APPEND. Para criar o arquivo caso ele não exista, use duas opções de uma vez:

Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

Como isso fica na prática

  • Você executa o programa com gravação (sobrescrita) — o arquivo terá duas linhas.
  • Executa o programa com anexação — a essas linhas são acrescentadas novas; as antigas permanecem no lugar.

3. Combinando opções: CREATE, APPEND, TRUNCATE_EXISTING

Em Java, é possível combinar várias opções para controlar com mais flexibilidade os modos de abertura do arquivo:

  • StandardOpenOption.CREATE — criar o arquivo, se ele não existir.
  • StandardOpenOption.APPEND — adicionar dados ao final.
  • StandardOpenOption.TRUNCATE_EXISTING — truncar o arquivo para zero bytes (limpar), se ele existir.
  • StandardOpenOption.CREATE_NEW — criar um novo arquivo; se já existir — erro.

Exemplo: criar o arquivo, se não existir, e adicionar dados ao final

Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);

Exemplo: criar o arquivo ou limpar completamente e gravar novos dados

Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

Tabela: principais modos de gravação

Opção(ões) Comportamento
(padrão) Criar arquivo ou sobrescrever o existente
APPEND
Adicionar ao final; erro se o arquivo não existir
APPEND + CREATE
Adicionar ao final; criar arquivo se ele não existir
TRUNCATE_EXISTING
Truncar o arquivo para zero e gravar novos dados
CREATE_NEW
Criar novo arquivo; erro se o arquivo já existir

4. Leitura e gravação de arquivos binários

Até agora trabalhamos com strings. Mas às vezes é necessário gravar ou ler bytes “brutos” (por exemplo, imagens, arquivos compactados, arquivos PDF). Nesse caso, use:

  • Files.readAllBytes(path) — lê o arquivo para um array de bytes.
  • Files.write(path, byteArray) — grava um array de bytes no arquivo.

Exemplo: copiando um arquivo

import java.nio.file.*;
import java.io.IOException;

public class CopyBinaryFileExample {
    public static void main(String[] args) throws IOException {
        Path source = Paths.get("logo.png");
        Path target = Paths.get("logo_copy.png");

        byte[] data = Files.readAllBytes(source);
        Files.write(target, data);

        System.out.println("Arquivo copiado!");
    }
}

Exemplo: anexando dados binários

byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

Atenção: anexar dados binários a um arquivo que já contém dados estruturados (por exemplo, uma imagem) geralmente irá corrompê-lo. Use anexação apenas para arquivos de texto ou arquivos binários especialmente preparados (por exemplo, logs).

5. Quando usar streams (FileInputStream, BufferedReader etc.)

Os métodos Files.readAllBytes(), Files.write() são convenientes para arquivos pequenos e médios, quando é possível carregar com tranquilidade todo o conteúdo na memória de uma vez. Se o arquivo for grande (por exemplo, gigabytes), ou se você quiser lê-lo linha a linha (por exemplo, para análise de logs), use streams.

Exemplo: leitura do arquivo linha a linha com BufferedReader

import java.nio.file.*;
import java.io.*;

public class ReadLinesExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Linha: " + line);
            }
        }
    }
}

Exemplo: gravação do arquivo linha a linha com BufferedWriter e anexação

import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class WriteAppendExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedWriter writer = Files.newBufferedWriter(
                path,
                StandardCharsets.UTF_8,
                StandardOpenOption.CREATE,
                StandardOpenOption.APPEND)) {
            writer.write("Mais uma linha via BufferedWriter!");
            writer.newLine();
        }
    }
}

6. Prática: criar um arquivo e anexar linhas a ele

Vamos juntar tudo em um único aplicativo. Suponha que temos um programa que mantém uma lista de tarefas (todo list) em um arquivo de texto. Toda vez que o usuário adiciona uma nova tarefa, nós a anexamos ao final do arquivo.

import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;

public class TodoList {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("todo.txt");
        Scanner scanner = new Scanner(System.in);

        System.out.print("Digite uma nova tarefa: ");
        String task = scanner.nextLine();

        // Anexamos a tarefa ao final do arquivo (criamos o arquivo, se ele não existir)
        Files.write(path, 
            List.of(task), 
            StandardOpenOption.CREATE, 
            StandardOpenOption.APPEND);

        System.out.println("Tarefa adicionada!");
    }
}

Execute o programa várias vezes — cada tarefa aparecerá no arquivo em uma nova linha. Pronto: temos nossa primeira todo list “permanente”!

7. Tratamento de erros: o que pode dar errado?

Trabalhar com arquivos sempre envolve risco de erros. Eis o que pode acontecer:

  • IOException — exceção básica para todos os erros de E/S. Pode ocorrer se o arquivo estiver em uso por outro programa, se não houver permissões de escrita/leitura, se o disco estiver cheio etc.
  • NoSuchFileException — se você tentar ler ou anexar a um arquivo que não existe (e não indicou a opção CREATE).
  • FileAlreadyExistsException — se você usar a opção CREATE_NEW e o arquivo já existir.

Recomendação: sempre envolva o trabalho com arquivos em um try-catch:

try {
    // suas operações com arquivos
} catch (IOException e) {
    System.out.println("Erro ao trabalhar com o arquivo: " + e.getMessage());
}

8. Erros comuns ao trabalhar com modos de abertura de arquivos

Erro nº 1: esqueceu de indicar a opção CREATE ao anexar. Se você tentar anexar (APPEND) a um arquivo que ainda não existe, receberá NoSuchFileException. Sempre adicione CREATE se quiser que o arquivo seja criado automaticamente.

Erro nº 2: sobrescrever o arquivo por engano. Chamar Files.write(path, data) sem opções adicionais destrói todo o conteúdo antigo do arquivo. Se quiser adicionar dados, e não apagar os antigos, use APPEND.

Erro nº 3: tentar anexar dados binários a um arquivo de texto (ou vice-versa). Se você misturar dados de texto e binários no mesmo arquivo, provavelmente obterá um arquivo ilegível. Sempre mantenha um formato por arquivo.

Erro nº 4: esqueceu de fechar o stream. Se usar streams (BufferedWriter, BufferedReader), não se esqueça de fechá-los (de preferência com try-with-resources), caso contrário o arquivo pode permanecer bloqueado.

Erro nº 5: não tratou as exceções. Qualquer operação com arquivos pode lançar IOException. Se você não tratar o erro, o programa encerrará de forma abrupta.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION