Leitura e Escrita de Arquivos em Java com FileInputStream e FileOutputStream
Escrita de Arquivos com a Classe FileOutputStream
A classe FileOutputStream é projetada para escrever bytes em um arquivo. Como uma subclasse de OutputStream, ela herda toda a sua funcionalidade.
O arquivo de destino é especificado no construtor da classe FileOutputStream. Existem diversas opções de construtores, sendo as mais comuns:
FileOutputStream(String filePath)
FileOutputStream(File fileObj)
FileOutputStream(String filePath, boolean append)
FileOutputStream(File fileObj, boolean append)O arquivo pode ser definido por meio de uma string com seu caminho ou por um objeto do tipo File. O segundo parâmetro, append, determina o modo de escrita: se for true, os novos dados serão adicionados ao final do arquivo existente; se for false, o conteúdo do arquivo será completamente sobrescrito.
A seguir, um exemplo de como gravar uma string em um arquivo:
import java.io.*;
public class Program {
public static void main(String[] args) {
String text = "Hello world!"; // Texto que será gravado no arquivo
try (FileOutputStream fos = new FileOutputStream("notes.txt")) {
// Converte a string em bytes usando o charset padrão da plataforma
byte[] buffer = text.getBytes();
fos.write(buffer, 0, buffer.length);
System.out.println("The file has been written");
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}Neste código, um objeto FileOutputStream é criado com o caminho do arquivo de destino. Se o arquivo não existir, ele será criado automaticamente. Como o objetivo é gravar uma string, primeiro ela é convertida em um array de bytes com o método getBytes(). Em seguida, o método write é chamado para gravar esses bytes no arquivo.
A utilização da estrutura try-with-resources para instanciar o FileOutputStream garante que o arquivo seja fechado e seus recursos liberados automaticamente ao final do bloco, mesmo que ocorram exceções.
Não é necessário gravar o array de bytes inteiro de uma vez. Uma versão sobrecarregada do método write() permite a escrita de um único byte:
fos.write(buffer[0]); // Grava apenas o primeiro byte do arrayLeitura de Arquivos com a Classe FileInputStream
Para ler dados de um arquivo, a classe FileInputStream é utilizada. Ela herda da classe InputStream e, portanto, implementa todos os seus métodos.
A forma mais comum de criar um objeto FileInputStream é por meio do construtor que aceita o caminho do arquivo como parâmetro:
FileInputStream(String fileName) throws FileNotFoundExceptionSe o arquivo não puder ser aberto, por exemplo, por não existir no caminho especificado, uma exceção do tipo FileNotFoundException será lançada.
O exemplo a seguir demonstra como ler os dados do arquivo gravado anteriormente e exibi-los no console:
import java.io.*;
public class Program {
public static void main(String[] args) {
try (FileInputStream fin = new FileInputStream("notes.txt")) {
int byteLido;
// Lê byte a byte até o final do arquivo (-1)
while ((byteLido = fin.read()) != -1) {
System.out.print((char) byteLido);
}
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}Neste caso, cada byte é lido individualmente. A expressão (byteLido = fin.read()) != -1 é um padrão comum em Java: o método read() lê um byte e retorna seu valor inteiro. Quando o método retorna -1, significa que o final do stream foi alcançado. Cada byte lido é então convertido para um caractere (char) e impresso no console.
Outra abordagem, mais eficiente para arquivos maiores, é ler os dados em um array de bytes, conhecido como buffer:
import java.io.*;
public class Program {
public static void main(String[] args) {
try (FileInputStream fin = new FileInputStream("notes.txt")) {
byte[] buffer = new byte[256];
System.out.println("File data:");
int bytesLidos;
while ((bytesLidos = fin.read(buffer)) != -1) {
// Imprime apenas os bytes que foram efetivamente lidos
for (int i = 0; i < bytesLidos; i++) {
System.out.print((char) buffer[i]);
}
}
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}Essa abordagem é mais eficiente, pois minimiza o número de chamadas ao sistema operacional para ler o arquivo. O método read(buffer) tenta preencher o array e retorna o número de bytes que foram efetivamente lidos. A variável bytesLidos armazena esse valor, garantindo que o laço for processe apenas os dados válidos, já que a última leitura pode não preencher o buffer completamente.
Exemplo Prático: Copiando um Arquivo
Combinando as duas classes, é possível ler dados de um arquivo e escrevê-los em outro, realizando uma cópia de forma eficiente.
import java.io.*;
public class Program {
public static void main(String[] args) {
try (FileInputStream fin = new FileInputStream("notes.txt");
FileOutputStream fos = new FileOutputStream("notes_new.txt")) {
byte[] buffer = new byte[256];
int bytesLidos;
// Lê um bloco de dados do arquivo de origem
while ((bytesLidos = fin.read(buffer)) != -1) {
// Grava o bloco lido no arquivo de destino
fos.write(buffer, 0, bytesLidos);
}
System.out.println("File has been copied");
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}Neste exemplo, o bloco try-with-resources gerencia ambos os streams. O laço while lê blocos de dados do arquivo de origem para o buffer e, em seguida, o método fos.write(buffer, 0, bytesLidos) grava exatamente a quantidade de bytes lida no arquivo de destino.
É fundamental notar que FileInputStream e FileOutputStream operam com bytes brutos. Por essa razão, são a escolha ideal para arquivos binários (como imagens, áudios ou executáveis). Para manipular arquivos de texto, onde a codificação de caracteres é crucial, as classes FileReader e FileWriter são mais apropriadas e seguras.
Resumo
FileOutputStreamé usado para escrever bytes em um arquivo, com a opção de adicionar dados ao final (append) ou sobrescrever o conteúdo.FileInputStreamé usado para ler bytes de um arquivo. O métodoread()retorna -1 para indicar o final do arquivo.- A leitura de dados em um buffer (um array de bytes) é mais eficiente do que a leitura byte a byte, pois reduz o número de acessos ao disco.
- A estrutura
try-with-resourcesé a forma recomendada para gerenciar streams, garantindo seu fechamento automático. - Embora funcionem para texto, essas classes são mais indicadas para dados binários, onde a manipulação de bytes brutos é necessária.