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 array
Leitura 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 FileNotFoundException
Se 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.