1. Introdução
Vamos começar pelo mais simples: em que um arquivo binário difere de um arquivo de texto? Um arquivo de texto é aquele que você pode abrir em um bloco de notas comum e ver letras, números, espaços e outros símbolos. Por exemplo, my_notes.txt ou poem.txt.
Arquivo binário — é um arquivo que não contém texto, mas bytes arbitrários. Pode ser uma imagem (.jpg, .png), música (.mp3), arquivo compactado (.zip), executável (.exe), vídeo (.mp4), um arquivo de banco de dados e assim por diante. Se você abrir esse tipo de arquivo no bloco de notas, verá algo como ÿØÿà ou uma parede de símbolos estranhos. Isso é normal! O computador “entende” apenas bytes — para ele, texto, imagem e vídeo são apenas uma sequência de bytes. Em arquivos de texto, esses bytes podem ser interpretados como caracteres; em binários, são dados “crus”, não destinados à leitura por pessoas.
Classes principais para trabalhar com arquivos binários
No Java, para trabalhar com arquivos binários são usados fluxos de bytes:
- InputStream — classe base para leitura de bytes.
- OutputStream — classe base para gravação de bytes.
Para trabalhar com arquivos existem as implementações específicas:
- FileInputStream — lê bytes de um arquivo.
- FileOutputStream — grava bytes em um arquivo.
Se você já ouviu falar de FileReader e FileWriter, saiba: eles trabalham com caracteres e servem apenas para texto. Para arquivos binários use apenas InputStream/OutputStream e seus derivados.
2. Leitura de arquivos binários
Leitura byte a byte
A maneira mais simples é ler o arquivo byte a byte. É didático, mas muito lento.
try (FileInputStream in = new FileInputStream("image.jpg")) {
int b;
while ((b = in.read()) != -1) {
// b é um número de 0 a 255 (byte); -1 indica o fim do arquivo
// Você pode processar o byte; por exemplo, somar todos os bytes
}
}
O método read() retorna o próximo byte como int (de 0 a 255) e, quando o arquivo termina, retorna -1. Normalmente lê-se byte a byte apenas quando é necessário algo muito específico (por exemplo, analisar a estrutura do arquivo).
Leitura em blocos (com buffer)
Ler byte a byte é como ir ao mercado buscar cada maçã separadamente. Muito mais eficiente é pegar logo um pacote! No Java, para isso há o método read(byte[] buffer), que preenche um array com bytes do arquivo.
try (FileInputStream in = new FileInputStream("image.jpg")) {
byte[] buffer = new byte[4096]; // buffer de 4 KB
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
// buffer contém bytesRead bytes do arquivo
// Você pode processar esses bytes; por exemplo, salvá-los em outro lugar
}
}
O método read(buffer) retorna quantos bytes foram realmente lidos (pode ser menor que o tamanho do buffer, especialmente na última leitura). Esse modo é muito mais rápido, porque há menos acessos ao disco.
Exemplo: copiando um arquivo
Vamos escrever um programa simples que copia qualquer arquivo binário (por exemplo, uma imagem) de um lugar para outro.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BinaryCopyExample {
public static void main(String[] args) {
String source = "cat.jpg";
String dest = "cat_copy.jpg";
try (FileInputStream in = new FileInputStream(source);
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192]; // 8 KB — um tamanho ótimo para a maioria dos casos
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("Cópia concluída!");
} catch (IOException e) {
System.out.println("Erro ao copiar: " + e.getMessage());
}
}
}
Simples: lemos blocos do arquivo de origem e os gravamos imediatamente no novo arquivo. Esse método funciona com quaisquer arquivos: imagens, compactados, vídeos.
3. Gravação de arquivos binários
Gravação de um array de bytes
Se você tem um array de bytes (por exemplo, que chegou da rede ou foi gerado no programa), pode gravá-lo em um arquivo assim:
byte[] data = new byte[] {1, 2, 3, 4, 5}; // exemplo de array
try (FileOutputStream out = new FileOutputStream("data.bin")) {
out.write(data); // grava todo o array no arquivo
}
O método write(byte[]) grava todos os bytes do array. Também é possível gravar apenas uma parte do array: out.write(data, offset, length).
Gravação de arquivo em partes (por exemplo, ao copiar)
Assim como na leitura, normalmente se usa um buffer:
try (FileInputStream in = new FileInputStream("source.bin");
FileOutputStream out = new FileOutputStream("dest.bin")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
Aqui, tudo o que lemos de um arquivo gravamos imediatamente em outro. Esse tipo de código é comum em aplicativos de compactação, gerenciadores de download, processadores de imagem etc.
4. Detalhes úteis
Por que não usar Reader/Writer para arquivos binários?
Reader e Writer trabalham com caracteres (char), não com bytes. Eles convertem bytes em caracteres automaticamente conforme a codificação (por exemplo, UTF-8). Isso é conveniente para texto, mas para arquivos binários — é fatal!
Se você tentar gravar uma imagem com FileWriter, obterá um arquivo corrompido que não poderá ser aberto. Lembre-se: para quaisquer arquivos não textuais use apenas InputStream/OutputStream!
Diferenças e detalhes importantes ao trabalhar com arquivos binários
- Tamanho do buffer: Um buffer muito pequeno deixa tudo mais lento (excessivos acessos ao disco), muito grande consome memória desnecessariamente. 4–16 KB — geralmente é o ideal.
- Tratamento de erros: Sempre trate IOException — o arquivo pode não existir, estar bloqueado ou o espaço em disco pode ter acabado.
- Fechamento de fluxos: Use try-with-resources — isso garante o fechamento dos arquivos mesmo em caso de erro.
- Sobrescrever arquivo: Se você abrir um arquivo com new FileOutputStream("file.bin"), ele será sobrescrito. Para acrescentar no final, use o construtor com o parâmetro append = true.
- Permissões de acesso: Se o programa não consegue abrir o arquivo, verifique as permissões de leitura/gravação.
- readAllBytes(): Permite ler o arquivo inteiro em um array de bytes com uma chamada. Para arquivos grandes — não use, para não “consumir” toda a memória!
5. Erros comuns ao trabalhar com arquivos binários
Erro nº 1: Uso de FileReader/FileWriter para arquivos binários. Isso resultará em corrupção de dados, porque essas classes convertem bytes em caracteres (e vice-versa), o que para imagens, arquivos compactados etc. é desastroso.
Erro nº 2: Ignorar o valor de retorno de read(). O método read(byte[]) pode ler menos bytes do que o solicitado, especialmente no último bloco. Sempre use o valor de retorno para saber quantos bytes foram realmente processados.
Erro nº 3: Esquecer de fechar o fluxo. Se o arquivo não for fechado, ele pode permanecer bloqueado e os dados podem não ser gravados completamente (principalmente na gravação!). Use try-with-resources.
Erro nº 4: Tentar ler o arquivo inteiro na memória sem considerar o tamanho. Para arquivos grandes, isso levará a OutOfMemoryError. Use um buffer e leia em partes.
Erro nº 5: Não tratar exceções. Trabalhar com arquivos pode sempre ocasionar erros: arquivo não encontrado, sem permissão, disco cheio. Não deixe de tratar IOException.
GO TO FULL VERSION