Você está na página 1de 20

Manipulação

 de  Arquivos  
 
Este material tem como objetivo introduzir o conceito de manipulação de
arquivos através da utilização das classes pertencentes ao pacote java.io,
disponibilizado na API da linguagem Java.
O uso de arquivos aumenta o poder e a flexibilidade de uma aplicação,
possibilitando que os dados manipulados sejam armazenados para uso futuro.
Imagine um editor de textos, por exemplo, onde toda vez que o programa
encerra sua execução, os dados digitados são perdidos. Certamente este
programa não seria utilizado por muitas pessoas. O mesmo vale para outros tipos
de programas também, como editores de imagens, editores de vídeo,
navegadores de internet e assim por diante. Apenas com estes exemplos, fica
bastante claro que a persistência de dados para uso futuro é parte essencial de
muitos programas.
Vamos, em um primeiro momento, estudar a classe File. Esta classe não
manipula os dados de um arquivo, mas sim seus metadados. Metadados de um
arquivo são dados referentes ao arquivo que não aqueles armazenados dentro
dele. Como exemplo de meta dados podemos citar a data de criação do arquivo,
o nome do arquivo, as permissões de acesso, o tamanho do arquivo e assim por
diante.

Classe  File  
 
A classe File está contida no pacote java.io. Desta maneira, toda vez que esta é
utilizada em um programa Java, esta deve ser importada deste pacote. Esta
classe permite que informações sobre o arquivo, que não seus dados
propriamente ditos, sejam obtidas.
De modo a demonstrar o funcionamento desta classe, considere o exemplo 1,
mostrado abaixo.
 
1   import java.io.File;
2  
3   public class UsoDaClasseFile {
4  
5   public static void main(String[] args) {
6  
7   File arquivo = new File("teste.txt");
8  
9   System.out.println("Nome: " + arquivo.getName());
10   System.out.println("Caminho: " + arquivo.getAbsolutePath())
11   System.out.println("Existe? " + arquivo.exists());
12   System.out.println("É um diretório? " + arquivo.isDirectory());
13   System.out.println("É um arquivo? " + arquivo.isFile());
14   System.out.println("Pode ser lido? " + arquivo.canRead());
15   System.out.println("Pode ser escrito? " + arquivo.canWrite());
16   System.out.println("Tamanaho do arquivo: " + arquivo.length());
17   }
18   }  
Exemplo  1  -­  Utilização  da  classe  File  

 
Este exemplo possui apenas uma classe de teste, chamada UsoDaClasseFile. Esta
classe, por sua vez, possui apenas um método main onde um objeto do tipo File é
declarado e instanciado na linha 5. O construtor utilizado recebe como parâmetro
uma String com o nome do arquivo (teste.txt neste caso).
Na linha 9 o método getName é utilizado para obter o nome do arquivo. Na linha
10 o método getAbsolutePath é utilizado para obter o caminho absoluto do
arquivo. Na linha 11 o método exists é utilizado para verificar se o arquivo existe.
Este método retorna true caso o arquivo já exista ou false caso contrario. Na
linha 12 o método isDirectory é utilizado para verificar se o arquivo é um
diretório. O método isFile utilizado na linha 13 verifica se o objeto representa um
arquivo comum. Nas linhas 14 e 15 os métodos canRead e canWrite são
utilizados para verificar se o arquivo pode ser lido ou escrito respectivamente. Na
linha 16 o método lenght é utilizado na obtenção do tamanho do arquivo em
bytes.
Note que, para utilizar a classe File, esta foi importada através do comando
import na linha 1.

A figura 1 mostra a saída deste programa quando este é utilizado com um


arquivo texto chamado teste.txt.
 

 
Figura  1  –  Saída  do  Exemplo  1  

A classe File também possui métodos para listar os arquivos de um diretório.


Considere o exemplo 2 mostrado abaixo. Neste exemplo, quando um diretório é
passado para o construtor da classe, o método list é utilizado para listar o
conteúdo do diretório (arquivos e também subdiretórios).
 
1   import java.io.File;
2  
3   public class ListaArquivos {
4  
5   public static void main(String[] args) {
6  
7   File f = new File("C:\teste\");
8  
9   if (f.isDirectory()){
10  
11   String[] arquivos = f.list();
12  
13   for (int i=0; i < arquivos.length; i++){
14   System.out.println(arquivos[i]);
15   }
16  
17   }
18   }
19   }
Exemplo  2  -­  Utilização  da  classe  File  para  listar  o  conteúdo  de  um  diretório.  

 
A figura 2 mostra a saída do programa do exemplo 2. Cada linha da saída é um
arquivo ou um subdiretório contido no diretório “C:\teste\”.
 
 
Figura  2  -­  Saída  do  exemplo  2  

A classe File possui outros métodos além dos mostrados nestes dois exemplos.
Mais detalhes sobre todos os métodos da classe File podem ser encontrados na
documentação da API da linguagem Java.

Arquivos  e  Fluxos  
Na linguagem Java arquivos são vistos como um fluxo sequencial de bytes como
mostra a figura 3. Cada sistema operacional tem um mecanismo diferente para
determinar o final de um arquivo, como um marcador de fim de arquivo ou uma
contagem do número de bytes do arquivo que é mantida juntamente com os
dados do arquivo. Um programa Java que processa um fluxo de bytes (arquivo)
simplesmente recebe uma indicação do sistema operacional quando o programa
alcança o fim do fluxo.

 
Figura  3  –  Um  arquivo  é  uma  sequencia  de  bytes.  

Um arquivo é considerado fonte quando realizamos uma leitura a partir dele e


destino quando realizamos a escrita nele. Ao fazermos uma leitura, os dados
trafegam da fonte (arquivo no disco) ordenados unidade a unidade até o
programa. Da mesma maneira, ao fazermos uma escrita em um arquivo, os
dados são enviados pelo programa ao destino de maneira ordenada.
Este processo de ler ou escrever os dados unidade a unidade é chamado de
acesso de fluxo, ou stream. Desta maneira podemos ter um fluxo de entrada e
também um fluxo de saída, dependendo da operação que estamos realizando no
arquivo. O fluxo de entrada é chamado de InputStream e o fluxo de saída é
chamado de OutputStream. Normalmente streams são tratados como dados
representados como números ou bytes, por isso são também chamados de leitura
e escrita binária.
Arquivos binários são aqueles que usam todo o conjunto de números para
representar uma informação, como por exemplo, uma planilha eletrônica, uma
foto ou um arquivo de música.
Existem ainda os arquivos que contém apenas caracteres, ou seja, letras,
números e alguns caracteres especiais utilizados para representar algo na tela ou
na impressora. A este tipo de arquivo damos o nome de arquivo texto. A
linguagem Java disponibiliza em sua API classes específicas para a leitura e
escrita de arquivos texto. O fluxo de entrada para um arquivo texto é chamado
de Reader e o fluxo de saída para um arquivo texto é chamado de Writer.
A API da linguagem Java organiza todas as classes para manipulação de arquivos
como mostram as figuras 4 e 5. Todas as classes utilizadas para manipulação de
arquivos texto são subclasses das classes abstratas Reader e Writer e todas as
classes utilizadas para manipulação de arquivos binários são subclasses das
classes abstratas InputStream e OutputStream.
 

 
Figura  4  -­  Classes  para  a  leitura  e  escrita  de  arquivos  binários  

 
Figura  5  -­  Classes  para  a  leitura  e  escrita  de  arquivos  texto  

As figuras 4 e 5 mostram apenas algumas das classes disponibilizadas pela API


da linguagem Java para a manipulação de arquivos. Mais informações sobre as
outras classes pertencentes à API podem ser encontradas na documentação da
API da linguagem.
Como dito anteriormente, as classes de streams são direcionais, ou seja, uma
stream deve ser utilizada para leitura ou para escrita de dados em um arquivo.
Não é possível utilizar uma única stream para ambos os casos.
O conceito de stream é utilizado na linguagem Java não apenas para a
manipulação de arquivos, mas também para qualquer tipo de entrada e saída que
um programa venha a realizar. Sempre que você imprime algum dado na tela
através do comando System.out.println, você está utilizando uma stream de
saída para direcionar, ou mostrar, os dados na tela do computador.
Este mesmo conceito é utilizado também para a comunicação através da rede, ou
ainda para a comunicação entre programas Java, comunicação com outros
dispositivos, etc.
Desta maneira, uma stream pode ser vista como uma abstração utilizada para
qualquer tipo de entrada e saída de dados que um programa venha a realizar
como mostra a figura 6.
 
 
Figura  6  -­  Streams  de  Entrada  e  Streams  de  Saída  

Todas as classes de stream para manipulação de arquivos em Java encontram-se


no pacote java.io, desta maneira, sempre que utilizarmos classes de streams
para manipulação de arquivos, temos que importar este pacote.
O acesso a arquivos em Java ainda pode ocorrer de maneira sequencial ou
aleatória. No acesso sequencial os dados do arquivo são lidos a partir do início.
Todos os dados são lidos de maneira consecutiva até que o dado procurado seja
encontrado ou até que o final do arquivo seja atingido. Este tipo de acesso não
permite a volta ao início do arquivo para uma nova leitura. Caso isso seja
necessário o arquivo deve ser fechado e aberto novamente. A maioria das classes
de streams disponibilizadas na API da linguagem Java são de acesso sequencial.
Quando um arquivo é acessado de maneira aleatória esta restrição não existe.
Neste tipo de acesso é possível ler um trecho do arquivo, voltar ao início ou a
qualquer outra posição do arquivo. A API da linguagem Java disponibiliza uma
classe para acesso aleatório: RandomAccessFile, que será descrita nas próximas
seções.

Manipulando  arquivos  texto  


Nesta seção vamos iniciar o estudo de streams manipulando um arquivo texto
chamado “texto.txt”. O conteúdo deste arquivo é mostrado abaixo:

1 Este é um texto de exemplo.


2 Esta é a segunda linha.
3 Esta é a terceira.
Figura  7  -­  Conteúdo  do  arquivo  textoExemplo.txt.

O programa mostrado no exemplo 3 mostra a classe Leitor. Esta classe possui um


método estático chamado ler que recebe como parâmetro um objeto do tipo File.
Este método lê o arquivo referenciado por este objeto utilizando para isso uma
stream de texto. O arquivo é lido caractere a caractere e o resultado é
armazenado em uma String que é retornada pelo método.
 
1   import java.io.*;
2  
3   public class Leitor {
4  
5   public static String ler(File arquivo){
6   StringBuffer sb = new StringBuffer();
7   try{
8   FileReader reader = new FileReader(arquivo);
9   int c;
10   do{
11   c = reader.read();
12   if (c != -1){
13   sb.append( (char) c );
14   }
15   } while (c != -1);
16   reader.close();
17   }
18   catch(IOException e){
19   e.printStackTrace();
20   }
21  
22   return sb.toString();
23   }
24   }
Exemplo  3  -­  Código  fonte  da  classe  Leitor  

Para realizar a leitura do arquivo passado como parâmetro foi utilizado um objeto
do tipo FileReader, declarado e instanciado na linha 8. A classe FileReader é uma
stream de leitura de arquivos em modo texto, isto é, os dados são lidos caractere
a caractere.
A leitura do arquivo é realizada no laço definido nas linhas 10 a 15. O método
read é utilizado na linha 11 para ler o arquivo caractere a caractere. Note que o
método read retorna um numero inteiro, que corresponde ao caractere lido do
arquivo. Cada caractere lido é armazenado em um StringBuffer1 na linha 13. O
laço termina sua execução quando o método read retornar -1, indicando que a
leitura chegou ao final do arquivo. Ao final da execução o método retorna a String
armazenada no objeto StringBuffer. Esta String contem todo o texto lido do
arquivo caractere a caractere.
O exemplo 4 mostra o código fonte da classe TestaLeitor que é utilizada para
testar a classe Leitor mostrada no exemplo 3 para realizar a leitura do arquivo
textoExemplo.txt.
 
1   import java.io.*;
2  
3   public class TestaLeitor {
4   public static void main(String[] args) {
5   File arquivo = new File("textoExemplo.txt");
6   String s = Leitor.ler(arquivo);
7   System.out.println(s);
8   }
9   }
Exemplo  4  -­  Código  fonte  da  classe  TestaLeitor.  

Embora este seja um exemplo bastante simples da utilização de streams, ele


demonstra de modo geral como a maioria das classes de streams da linguagem
Java funcionam. A classe   FileReader   utilizada no exemplo 3 é de acesso
sequencial, isto é, a leitura do arquivo é realizada de maneira sequencial do inicio
ao fim do arquivo. A cada chamada do método read na linha 11 a leitura avança
um caractere no arquivo. Outro ponto importante é que sempre que utilizamos
uma stream, seja para leitura ou para escrita de arquivo, devemos fechá-la
realizando uma chamada ao método close, como foi feito na linha 16.

                                                                                                               
1  A classe StringBuffer é um como uma String, mas que pode ser modificada

através da chamada de métodos específicos, como o método append utilizado no


exemplo 3.
A escrita em um arquivo texto pode ser implementada de maneira semelhante à
leitura através do uso de streams. Considere o exemplo 5, onde é definida a
classe Escritor. Esta classe possui um método estático chamado escreve que
recebe como parâmetro um objeto do tipo File e uma String com o texto a ser
escrito no arquivo.

1 import java.io.*;
2
3 public class Escritor {
4
5 public static void escreve(File arquivo, String texto){
6
7 try {
8 FileWriter writer = new FileWriter(arquivo);
9 char c[] = texto.toCharArray();
10
11 for (int cont=0; cont < c.length; cont++){
12 writer.write(c[cont]);
13 }
14 writer.close();
15 } catch (IOException e) {
16 e.printStackTrace();
17 }
18 }
19 }
Exemplo  5  -­  Código  fonte  da  classe  Escritor

Para realizar a escrita no arquivo referenciado pelo objeto do tipo File uma
stream do tipo FileWriter foi declarada e instanciada na linha 8. Na linha 9 a
String passada como parâmetro é convertida para um vetor de char (tipo
primitivo). O laço das linhas 11 a 13 itera por este vetor escrevendo caractere a
caractere o conteúdo do vetor no arquivo. Ao final do laço, na linha 14, a stream
é fechada. Note que, assim como no método ler da classe Leitor, o código que
manipula o arquivo está dentro de um bloco try. Isso é necessário pois
praticamente todas as classes de Streams podem lançar exceções e estas devem,
obrigatoriamente, ser tratadas.
O exemplo 6 mostra a utilização da classe Escritor na escrita de um arquivo
texto.

1 import java.io.*;
2
3 public class TestaEscritor {
4 public static void main(String[] args) {
5 File arquivo = new File("saida.txt");
6 String texto = "Este texto vai ser gravado no arquivo!!";
7 Escritor.escreve(arquivo, texto);
8 }
9 }
Exemplo  6  -­  Código  fonte  da  classe  TestaEscritor.

Na linha 5 um objeto do tipo File é declarado e instanciado. Na linha 6 uma String


é criada com o texto que será escrito no arquivo. Na linha 7 o método escreve da
classe Escritor é utilizado para escrever o texto no arquivo. Após a execução
deste programa um arquivo chamado saída.txt será criado com apenas uma linha
de texto.
 
Streams  com  Buffer2  
 
Os métodos de leitura e escrita de arquivos textos mostrados nos exemplos 3 e 5
não são muito eficientes pois a cada chamada dos métodos read e write um
acesso a disco é realizado. De modo a otimizar o acesso a disco, a API da
linguagem Java disponibiliza classes que podem ser utilizadas como objetos
intermediários que se encarregarão de ler/escrever os dados do arquivo,
armazená-los em sua memória interna e convertê-los de acordo para a aplicação
realiza a leitura/escrita. Com a utilização destes objetos, cada vez que uma
leitura ou escrita é feita em um arquivo, os dados são lidos/escritos em um buffer
interno do objeto, otimizando assim tanto a leitura quanto a escrita.
Considere o exemplo 7 onde a classe LeitorBuffer é mostrada. Esta classe
implementa a mesma funcionalidade da classe Leitor do exemplo 3. Neste
exemplo, entretanto, um objeto do tipo BufferedReader é utilizado em conjunto
com o objeto FileReader para realizar a leitura.

1 import java.io.*;
2
3 public class LeitorComBuffer {
4
5 public static String ler(File arquivo) {
6 StringBuffer sb = new StringBuffer();
7
8 try{
9
10 FileReader reader = new FileReader(arquivo);
11 BufferedReader bufReader = new BufferedReader(reader);
12
13 String linha;
14
15 while ((linha = bufReader.readLine()) != null){
16 sb.append(linha + "\r\n");
17 }
18
19 bufReader.close();
20 }
21 catch(IOException e){
22 e.printStackTrace();
23 }
24
25 return sb.toString();
26 }
27 }
Exemplo  7  -­  Código  fonte  da  classe  LeitorComBuffer

Na linha 10 um objeto do tipo FileReader é declarado e instanciado como no


exemplo 3. Na linha 11 um objeto do tipo BufferedReader é declarado e
instanciado recebendo como parâmetro em seu construtor o objeto FileReader
instanciado na linha 10. Note que a leitura realizada na linha 15 utiliza o método
readLine da classe BufferedReader para realizar a leitura. A cada chamada deste
método uma linha do arquivo texto é retornada e armazenada na variável linha.
Cada linha do arquivo é armazenada no objeto sb na linha 16. O laço é executado
até que o método readLine retorne null, indicando que o arquivo chegou ao fim.
Note que neste caso a leitura não é mais realizada caractere a caractere como no
exemplo 3. A cada chamada do método readLine uma linha inteira do arquivo é
lida e armazenada no buffer interno do objeto bufReader.

                                                                                                               
2   Um   buffer é uma área de memória utilizada para otimizar a transferência de

dados.
O exemplo 8 mostra a mesma abordagem utilizada para a escrita em um arquivo
texto, onde um objeto do tipo BufferedWriter é utilizado em conjunto com um
objeto do tipo FileWriter para realizar a escrita.

1 import java.io.*;
2
3 public class EscritorComBuffer {
4 public static void escreve(File arquivo, String texto){
5 try{
6 FileWriter writer = new FileWriter(arquivo);
7 BufferedWriter bufWriter = new BufferedWriter(writer);
8 bufWriter.write(texto);
9 bufWriter.close();
10 }
11 catch(IOException e){
12 e.printStackTrace();
13 }
14 }
15 }
Exemplo  8  -­  Código  fonte  da  classe  EscritorComBuffer  

Neste exemplo um objeto do tipo FileWriter é declarado e instanciado na linha 6.


Na linha 7 um objeto do tipo BufferedWriter é declarado e instanciado recebendo
como parâmetro o objeto FileWriter da linha 6. Na linha 8 a escrita é realizada
através da chamada do método write da classe BufferedWriter. Neste momento o
texto armazenado na variável texto é escrito na memória interna do objeto
BufferedWriter. O texto só é efetivamente gravado no arquivo na linha 9, onde o
método close é chamado e os dados são enviados da memória para o arquivo.
Caso o método close não fosse chamado, nada seria escrito no arquivo, por isso é
muito importante sempre fechar as streams após o termino da manipulação dos
arquivos. Esta abordagem é mais eficiente que a abordagem utilizada no exemplo
5, onde um caractere era escrito por vez no disco.
O exemplo 9 mostra como ambas as classes, LeitorComBuffer e
EscritorComBuffer podem ser utilizadas para copiar um arquivo texto.

1 import java.io.*;
2
3 public class CopiadorBuffer {
4 public static void main(String[] args) {
5 File entrada = new File("entrada.txt");
6 File saida = new File("copia.txt");
7 String s = LeitorComBuffer.ler(entrada);
8 EscritorComBuffer.escreve(saida, s);
9 }
10 }
Exemplo  9  -­  Código  fonte  da  classe  CopiadorBuffer  

Neste exemplo o conteúdo do arquivo texto entrada.txt é lido com o método ler
da classe LeitorComBuffer e armazenado na String s na linha 7. Esta String é
então utilizada para gerar o arquivo copia.txt na linha 8.

Quebrando  linhas  com  o  método  split  


Uma situação bastante comum no que diz respeito à leitura de arquivos texto,
consiste em ler um arquivo texto linha a linha e, para cada linha lida do arquivo,
realizar algum processamento.
Suponha um arquivo texto com os dados dos alunos de uma turma organizados
como na figura 8. Neste arquivo temos um aluno por linha e os dados de cada
aluno estão separados por uma vírgula. No caso deste arquivo temos o número
de matrícula, seguido do nome e da data de nascimento.
Vamos agora implementar uma aplicação simples que lê os dados dos alunos do
arquivo e, para cada aluno, instancia um objeto do tipo Aluno e o insere em um
vetor de alunos.

1 0000001, João da Silva, 10/10/1980


2 0000002, Andrew T., 12/06/1977
3 0000003, Donald Knuth, 09/08/1987
4 0000004, Robert Lafore, 15/07/1979
5 0000005, Alfred Aho, 01/03/1965
Figura  8  –  Arquivo  alunos.txt  

O código fonte da classe Aluno é mostrado no exemplo 10.

1 public class Aluno {


2
3 protected String matricula;
4 protected String nome;
5 protected String dataNascimento;
6
7 public Aluno(String matricula, String nome, String dataNascimento) {
8 this.matricula = matricula;
9 this.nome = nome;
10 this.dataNascimento = dataNascimento;
11 }
12
13 public String getMatricula() {
14 return matricula;
15 }
16
17 public void setMatricula(String matricula) {
18 this.matricula = matricula;
19 }
20
21 public String getNome() {
22 return nome;
23 }
24
25 public void setNome(String nome) {
26 this.nome = nome;
27 }
28
29 public String getDataNascimento() {
30 return dataNascimento;
31 }
32
33 public void setDataNascimento(String dataNascimento) {
34 this.dataNascimento = dataNascimento;
35 }
36
37 public String toString(){
38 return String.format("Matricula: %s\nNome: %s\nData de Nascimento: %s\r\n",
39 getMatricula(),
40 getNome(),
41 getDataNascimento());
42 }  
43 }
Exemplo  10  -­  Código  fonte  da  classe  Aluno  
Nossa aplicação deve ler o arquivo alunos.txt para cada linha dele instanciar um
objeto do tipo Aluno. Para realizar a leitura do arquivo iremos utilizar as classes
FileReader e BufferedReader como no exemplo 7.
Para quebrar cada linha do arquivo vamos utilizar o método split da classe String.
Este método recebe como parâmetro um caractere separador e retorna um vetor
de Strings onde cada posição do vetor contém uma substring da String que
chamou o método. Um exemplo de utilização do método split é mostrado na
figura 9.

 
Figura  9  -­  Exemplo  de  utilização  do  método  split.  

Note que o conteúdo do vetor corresponde a String str separada segundo o


caractere separador (“,“).
A classe principal de nossa aplicação será a classe CarregaAlunos. Esta classe
terá um método chamado constroiAluno, que irá receber como parâmetro uma
linha do arquivo e irá retornar um objeto do tipo Aluno com os dados
correspondentes.
O código fonte da aplicação é mostrado no exemplo 11. Alem do método
constroiAluno, a classe CarregaAluno também possui o método carregaArquivo,
que recebe como parâmetro o nome do arquivo de alunos a ser carregado e
retorna um vetor de objetos do tipo Aluno.
Também vamos implementar nesta classe um método main para testar os
métodos constroiAluno e carragaArquivo.
O código fonte da classe CarregaAlunos é mostrado no exemplo 11.

1 import java.io.*;
2
3 public class CarregaAlunos {
4
5 protected static Aluno constroiAluno(String linha){
6 String atributos[] = linha.split(",");
7 Aluno a = new Aluno(atributos[0], atributos[1], atributos[2]);
8 return a;
9 }
10
11 public static Aluno[] carregaArquivo(String arquivo) throws IOException{
12 Aluno alunos[] = new Aluno[5];
13 FileReader fr = new FileReader(arquivo);
14 BufferedReader br = new BufferedReader(fr);
15
16 String linha;
17 int cont = 0;
18 while ((linha = br.readLine()) != null){
19 Aluno a = constroiAluno(linha);
20 alunos[cont] = a;
21 cont++;
22 }
23 br.close();
24 return alunos;
25 }
26
27 public static void main(String[] args) {
28 try{
29 Aluno a[] = carregaArquivo("alunos.txt");
30 for (int i=0; i < a.length; i++){
31 System.out.println(a[i]);
32 }
33 }
34 catch(IOException e){
35 e.printStackTrace();
36 }
37 }
38 }
Exemplo  11  -­  Código  fonte  da  classe  CarregaAlunos

No método carregaArquivo, por questões de simplicidade, estamos assumindo


que teremos exatamente cinco alunos no arquivo alunos.txt. Ainda neste método
o arquivo é lido linha a linha no laço das linhas 18 a 22. Para cada linha do
arquivo o método constroiAluno é chamado e o objeto do tipo Aluno retornado é
armazenado no vetor alunos.
No método main do exemplo 11 o método carregaArquivo é chamado recebendo
como parâmetro o arquivo “alunos.txt”, que neste caso deve estar no mesmo
diretório do programa sendo executado. A chamada da linha 29 retorna um vetor
de objetos do tipo Aluno contendo 5 objetos. Cada um dos objetos corresponde a
um aluno lido do arquivo.
Embora este seja um exemplo bastante simples, ele denota uma tarefa bastante
comum no que diz respeito à manipulação de arquivos texto, uma vez que
abrimos o arquivo, lemos seu conteúdo e realizamos um processamento no
conteúdo lido.

Manipulando  arquivos  binários  


Até agora tratamos apenas de manipulação de arquivos texto. Existem situações,
entretanto, onde se faz necessária a manipulação de arquivos binários, como por
exemplo, uma imagem, um arquivo de vídeo, uma apresentação, etc. Sempre
que precisamos manipular um arquivo deste tipo devemos utilizar as classes de
stream apropriadas, que são todas as classes que são subclasses de InputStream
e OutputStream, mostradas na figura 4.
A fim de exemplificar a manipulação de arquivos binários, considere o exemplo
12 onde as classes FileInputStream e FileOutputStream são utilizadas para
realizar a copia de um arquivo.

1 import java.io.*;
2
3 public class CopiadorBinario {
4
5 public static void copia(File entrada, File saida){
6 try{
7 FileInputStream input = new FileInputStream(entrada);
8 FileOutputStream output = new FileOutputStream(saida);
9 int c;
10 while ((c=input.read()) != -1){
11 output.write(c);
12 }
13 input.close();
14 output.close();
15 }
16 catch(IOException e){
17 e.printStackTrace();
18 }
19 }
20 }
Exemplo  12  -­  Código  fonte  da  classe  CopiadorBinario

O método estático copia recebe como parâmetro dois objetos do tipo File. O
parâmetro “entrada” corresponde ao arquivo de origem e o parâmetro “saída”
corresponde ao arquivo de destino. A cópia do arquivo ocorre no laço das linhas
10 a 12. A cada iteração deste laço um byte é lido do arquivo de origem e escrito
no arquivo de destino. O laço termina quando o valor lido do arquivo de origem
for -1, que indica que o arquivo chegou ao seu final. Após a execução do laço
ambas as streams são fechadas nas linhas 13 e 14.
O exemplo 13 mostra a utilização da classe CopiadorBinario na copia de um
arquivo que contem uma imagem.

1 import Java.io.*;
2
3 public class TestaCopiadorBinario {
4
5 public static void main(String[] args) {
6
7 File entrada = new File("streams.jpg");
8 File saida = new File ("copia de streams.jpg");
9
10 System.out.println("Iniciando a cópia...");
11 CopiadorBinario.copia(entrada, saida);
12 System.out.println("Copia concluída!");
13 }
14 }
Exemplo  13  -­  Código  fonte  da  classe  TestaCopiadorBinário.

Outras classes bastante utilizadas para a manipulação de arquivos binários são as


classes DataInputStream e DataOutputStream. Estas classes possibilitam a
leitura e escrita de tipos primitivos da linguagem Java em arquivos. A tabela 1
mostra alguns métodos da classe DataInputStream.

Método Descrição
readByte Lê um byte do arquivo.
readChar Lê um char do arquivo.
readDouble Lê um double do arquivo.
readFloat Lê um float do arquivo.
readInt Lê um inteiro do arquivo.
readLong Lê um long do arquivo.
readShort Lê um short do arquivo.
readUTF Lê uma sequencia de caracteres e
retorna um objeto do tipo String.
Tabela  1  -­  Alguns  métodos  da  classe  DataInputStream,  utilizada  para  a  leitura  de  tipos  primitivos.  

A tabela 2 mostra alguns métodos da classe DataOutputStream.

Método Descrição
writeByte Escreve um byte no arquivo.
writeChar Escreve um char no arquivo.
writeDouble Escreve um double no arquivo.
writeFloat Escreve um float no arquivo.
writeInt Escreve um inteiro no arquivo.
writeLong Escreve um long no arquivo
writeShort Escreve um short no arquivo.
writeUTF Recebe uma String como parâmetro e
escreve os caracteres correspondentes
no arquivo.
Tabela  2  -­  Alguns  métodos  da  classe  DataOutputStream.

O exemplo 14 mostra a utilização da classe DataOutputStream na escrita de um


conjunto de números inteiros em um arquivo.

1 import java.io.*;
2
3 public class EscreveInteiros {
4
5 public static void escreveInteiros(int v[], File arquivo) throws IOException{
6 FileOutputStream fos = new FileOutputStream(arquivo);
7 DataOutputStream dos = new DataOutputStream(fos);
8
9 for(int i=0; i<v.length; i++){
10 dos.writeInt(v[i]);
11 }
12 dos.close();
13 fos.close();
14 }
15
16 public static void main(String[] args) {
17
18 File arquivo = new File("inteiros.bin");
19 int numeros[] = new int[10];
20 for (int i=0; i<10; i++){
21 numeros[i] = i + 1;
22 }
23 try{
24 escreveInteiros(numeros, arquivo);
25 }
26 catch(IOException e){
27 e.printStackTrace();
28 }
29 }
30 }
Exemplo  14  –  Código  fonte  da  classe  EscreveInteiros.  

O método escreveInteiros definido nas linhas 5 a 14 recebe como parâmetro um


vetor de números inteiros com os números a serem escritos no arquivo e uma
referência a um objeto do tipo File. Na linha 6 um objeto do tipo
FileOutputStream é declarado e instanciado recebendo como parâmetro o objeto
do tipo File. Na linha 7 um objeto do tipo DataOutputStream é declarado e
instanciado recebendo como parâmetro o objeto fis. O laço das linhas 9 a 11 itera
pelo vetor de inteiros escrevendo cada um dos números no arquivo. Esta classe
possui ainda um método main onde um objeto do tipo File é declarado e
instanciado na linha 18. Em seguida um vetor de inteiros é declarado e
inicializado. O método escreveInteiros, chamado na linha 24, escreve o conteúdo
do vetor no arquivo inteiros.bin. Note que este arquivo é um arquivo binário e
não texto. Não conseguimos visualizar seu conteúdo de maneira adequada em
um editor de textos.

O exemplo 15 mostra como o arquivo gerado no exemplo 14 pode ser lido


utilizando para isso a classe DataInputStream. Este exemplo utiliza a mesma
lógica do exemplo 14. Para que este exemplo funcione corretamente, o arquivo
“inteiros.bin” já deve ter sido gerado através da execução do exemplo 14

1 import java.io.*;
2
3 public class LeInteiros {
4 public static int[] leInteiros(File arquivo) throws IOException{
5 FileInputStream fis = new FileInputStream(arquivo);
6 DataInputStream dis = new DataInputStream(fis);
7
8 int vetor[] = new int[10];
9 for (int i=0; i<10; i++){
10 vetor[i] = dis.readInt();
11 }
12 dis.close();
13 fis.close();
14 return vetor;
15 }
16
17 public static void main(String[] args) {
18 File arquivo = new File("inteiros.bin");
19 try{
20 int numeros[] = leInteiros(arquivo);
21 for (int i=0; i<numeros.length; i++){
22 System.out.println(numeros[i]);
23 }
24 }
25 catch(IOException e){
26 e.printStackTrace();
27 }
28 }
29 }
Exemplo  15  -­  Código  fonte  da  classe  LeInteiros  

O método leInteiros definido nas linhas 4 a 15 declara e instancia um objeto do


tipo FileInputStream na linha 5 passando como parâmetro para o construtor um
objeto do tipo File. Na linha 6 um objeto do tipo DataInputStream é declarado e
instanciado recebendo como parâmetro no seu construtor o objeto declarado na
linha 5. Em seguida, na linha 8 um vetor de inteiros com 10 posições é criado. O
laço das linhas 9 a 11 utiliza o objeto dis para ler os números inteiros do arquivo
e armazená-los no vetor. A cada chamada do método readInt na linha 10 um
novo número é lido do arquivo “inteiros.bin“ e é armazenado no vetor.
O método main definido nas linhas 17 a 28 cria um objeto do tipo File para o
arquivo “inteiros.bin” na linha 18. Na linha 20 o método leInteiros é chamado
recebendo como parâmetro o objeto declarado e instanciado na linha 18. Este
método retorna uma vetor de números inteiros com os números lidos a partir do
arquivo. O laço das linhas 21 a 23 mostra na tela o conteúdo do vetor. Note que
a chamada ao método leInteiros está dentro de um bloco try. Isto é necessário
pois uma exceção do tipo IOException pode ser lançada por este método.

Acesso  Randômico  
Todas as classes de streams vistas até o momento são classes de acesso
sequencial, ou seja, não podemos, depois de ter aberto o arquivo, voltar para o
início dele ou para qualquer outra posição para ler ou escrever novamente.
Para suportar a leitura e escrita de arquivos com acesso aleatório, a linguagem
Java disponibiliza em sua API a classe RandomAccessFile. Instancias desta classe
suportam tanto a leitura quanto a escrita de maneira aleatória em um arquivo.
Tanto a leitura quanto a escrita ocorrem em modo binário. Esta classe trata um
arquivo em disco como um grande vetor de bytes. Para isso ela utiliza um cursor,
ou índice, para marcar a posição corrente do arquivo. Este índice também é
chamado de ponteiro do arquivo (file pointer em inglês) e é utilizado para indicar
em que posição do arquivo a próxima leitura ou escrita irá ocorrer. A cada
operação de leitura ou escrita o ponteiro do arquivo é avançado de acordo com a
quantidade de bytes lidos ou escritos.
Como esta classe suporta tanto a leitura quanto a escrita, ela pode ser criada
somente para leitura, somente para escrita ou ainda para ambos os modos,
leitura e escrita. Se uma operação de escrita ultrapassar o final do arquivo, o
tamanho deste é aumentado de acordo. Se uma operação de leitura ultrapassar o
tamanho do arquivo uma exceção do tipo EOFException é lançada.
O exemplo 16 demonstra a utilização da classe RandomAccessFile.

1 import java.io.*;
2
3 public class ExemploRAF {
4 public static void main(String[] args) {
5 RandomAccessFile raf;
6 try{
7
8 raf = new RandomAccessFile("raf.txt", "rw");
9
10 raf.writeBytes("Primeira linha do arquivo.\n");
11 raf.writeBytes("Segunda linha do arquivo\n");
12
13 raf.seek(0);
14 String s = raf.readLine();
15 System.out.println("Linha 1: " + s);
16 long tamanho_arquivo = raf.length();
17 raf.seek(tamanho_arquivo);
18 raf.writeBytes("Última linha do arquivo.");
19 raf.close();
20 }
21 catch (IOException e){
22 e.printStackTrace();
23 }
24 }
25 }
Exemplo  16  -­  Utilização  da  classe  RandomAccessFile

Um objeto do tipo RandomAccessFile é declarado na linha 5. Na linha 8 este


objeto é instanciado e seu construtor recebe como parâmetro o nome do arquivo,
“raf.txt” e o modo como este arquivo deve ser aberto. Neste exemplo o arquivo
está sendo aberto tanto para leitura quanto para escrita (“rw”). Para abrir o
arquivo somente para leitura devemos passar apenas “r” como parâmetro. Para
abrir somente para escrita devemos passar apenas “w”. Nas linhas 10 e 11 o
método writeBytes é utilizado para escrever duas linhas de texto no arquivo. Na
linha 13 o método seek é utilizado para posicionar o ponteiro do arquivo no byte
0, isto é, no início do arquivo. A chamada ao método readline na linha 14 lê uma
linha de texto do arquivo e armazena na String s. Uma linha neste caso é
delimitada pelo caractere “\n”. Na linha 16 o método length é utilizado para obter
o tamanho do arquivo em bytes. Este valor é utilizado então na linha 17 para
posicionar o ponteiro do arquivo no seu final. Na linha 18 uma nova linha de texto
é escrita e o arquivo é fechado na linha 19.
Este exemplo demonstrou como a classe RandomAcessFile pode ser utilizada para
ler e escrever em diferentes posições em um arquivo binário. Esta classe pode ser
utilizada também quando quisermos manipular um arquivo contendo registros de
tamanho fixo. Sabendo o tamanho do registro podemos localizar diretamente
qualquer um dos registros do arquivo.

Lendo  e  Escrevendo  Objetos  


 
A linguagem Java disponibiliza em sua API classes para a leitura e gravação de
objetos em disco. Este conceito é chamado de serialização e consiste em salvar o
estado de um objeto (seus atributos) em disco para posterior recuperação. Desta
maneira podemos salvar um objeto em disco para, posteriormente, recuperá-lo.
Um objeto serializado é um objeto representado como uma sequencia de bytes
que inclui os dados do objeto, bem como as informações sobre o tipo do objeto e
os tipos dos dados armazenados no objeto. Depois que um objeto serializado foi
salvo em um arquivo, este pode ser lido e desserializado - isto é, as informações
dos tipos e bytes que representam o objeto e seus dados podem ser utilizadas
para recriar o objeto na memória.
As classes disponibilizadas pela API da linguagem Java para leitura e escrita de
objetos em arquivos são as classes ObjectInputStream e ObjectOutputStream
respectivamente. A classe ObjectInputStream implementa a interface
ObjectInput e a classe ObjectOutputStream implementa a interface ObjectOutput.
A interface ObjectOutput define o método writeObject que recebe como
parâmetro um objeto do tipo Object que deve implementar a interface
Serializable (explicada a seguir) e grava suas informações em uma stream de
saída. De maneira correspondente, a interface ObjectInput define o método
readObject que lê e retorna uma referência a um objeto do tipo Object a partir de
uma stream de entrada. Depois que o objeto foi lido podemos fazer a conversão
(com o operador de cast) para o tipo real do objeto.
Para que um objeto possa ser escrito ou lido a partir de uma stream, ele deve
implementar a interface Serializable. Esta interface é uma interface de marcação
pois não possui nenhum método definido. Ela é utilizada somente para “marcar”
um objeto como serializável. Esta característica é muito importante pois a classe
ObjectOutputStream não envia para uma stream de saída um objeto que não
implemente esta interface.
Em uma classe que implementa a interface Serializable, devemos assegurar que
cada variável da classe também seja do tipo Serializable. Por padrão todas as
variáveis de tipo primitivo são serializáveis. Para variáveis que são referências a
outros objetos devemos verificar na definição da classe (e possivelmente nas
superclasses) para assegurar que estas implementam a interface Serializable. Por
padrão vetores são objetos do tipo Serializable. Entretanto, se o vetor contiver
referências a outros objetos estes objetos podem ou não ser serializáveis.
De modo a exemplificar o funcionamento da serialização, considere a classe
Pessoa, mostrada no exemplo 17 e a classe DataDeNascimento, mostrada no
exemplo 18. Para que esta classe possa ser salva em um arquivo ela deve,
obrigatoriamente, implementar a interface Serializable.

1 import java.io.*;
2
3 public class Pessoa implements Serializable{
4
5 protected String nome;
6 DataDeNascimento d;
7
8 public Pessoa(String nome, int dia, int mes, int ano){
9 this.nome = nome;
10 d = new DataDeNascimento(dia, mes, ano);
11 }
12
13 public String getNome() {
14 return nome;
15 }
16
17 public String getDataDeNascimento() {
18 return d.toString();
19 }
20 }
Exemplo  17  -­  Código  fonte  da  classe  Pessoa

Esta classe implementa a interface Serializable em sua definição (linha 3). Ela
possui como atributos uma String que representa o nome da pessoa e também
um objeto do tipo DataDeNascimento que representa uma data de nascimento.
Como esta classe é utilizada como tipo de um atributo da classe Pessoa, esta
também deve implementar a interface Serializable, como mostra o exemplo 18.

1 import java.io.*;
2
3 public class DataDeNascimento implements Serializable {
4
5 protected int dia;
6 protected int mes;
7 protected int ano;
8
9 public DataDeNascimento(int dia, int mes, int ano) {
10 this.dia = dia;
11 this.mes = mes;
12 this.ano = ano;
13 }
14
15 public int getDia() {
16 return dia;
17 }
18
19 public void setDia(int dia) {
20 this.dia = dia;
21 }
22
23 public int getMes() {
24 return mes;
25 }
26
27 public void setMes(int mes) {
28 this.mes = mes;
29 }
30
31 public int getAno() {
32 return ano;
33 }
34
35 public void setAno(int ano) {
36 this.ano = ano;
37 }
38
39 public String toString() {
40 return String.format("%d/%d/%d", getDia(), getMes(), getAno());
41 }
42 }
Exemplo  18  -­  Código  fonte  da  classe  DataDeNascimento.

O exemplo 19 mostra o código fonte da classe TestaSerializacao. Esta classe


demonstra como um objeto do tipo Pessoa pode ser escrito em um arquivo e
posteriormente lido. O método gravaObjeto, definido nas linhas 4 a 14 recebe
como parâmetro uma referência a um objeto do tipo File e também uma
referência a um Object, que é o objeto que será salvo no arquivo. Na linha 6 um
objeto do tipo FileOutputStream é declarado e instanciado recebendo como
parâmetro em seu construtor o objeto f. Na linha 7 um objeto do tipo
ObjectOutputStream é declarado e instanciado recebendo como parâmetro em
seu construtor o objeto fos. Em seguida, na linha 8, o método writeObject é
chamado recebendo como parâmetro o objeto o. Este método escreve no arquivo
o objeto referenciado por o. Lembre-se que este objeto deve implementar a
interface Serializable. Do contrário nada é escrito no arquivo. Na linha 9 a stream
é fechada. Como nos outros exemplos onde houve manipulação de arquivos, o
código que abre o arquivo e escreve o objeto está dentro de um bloco try.
1 import java.io.*;
2
3 public class TestaSerializacao {
4 protected static void gravaObjeto(File f, Object o) {
5 try{
6 FileOutputStream fos = new FileOutputStream(f);
7 ObjectOutputStream oos = new ObjectOutputStream(fos);
8 oos.writeObject(o);
9 oos.close();
10 }
11 catch(IOException e){
12 System.out.println("Erro ao abrir o arquivo: " + e);
13 }
14 }
15
16 protected static Object leObjeto(File f) {
17 Object o = null;
18 try{
19 FileInputStream fis = new FileInputStream(f);
20 ObjectInputStream ois = new ObjectInputStream(fis);
21 o = ois.readObject();
22 ois.close();
23 }
24 catch(IOException e){
25 System.out.println("Erro ao abrir o arquivo: " + e);
26 }
27 catch(ClassNotFoundException e){
28 System.out.println("Objeto não encontrado");
29 }
30 return o;
31 }
32
33 public static void main(String[] args) {
34 Pessoa p = new Pessoa("João da Silva", 5, 8, 1975);
35 File f = new File("ArquivoPessoa.arq");
36 gravaObjeto(f, p);
37
38 Pessoa p2 = (Pessoa) leObjeto(f);
39
40 System.out.println("Foi armazenado o objeto pessoa com os valores: ");
41 System.out.println("Nome: " + p2.getNome());
42 System.out.println("Data de Nascimento: " + p2.getDataDeNascimento());
43 }
44 }
Exemplo  19  -­  Código  fonte  da  classe  TestaSerializacao

O método leObjeto, definido nas linhas 16 a 31 recebe como parâmetro uma


referência a um objeto do tipo File e retorna uma referência a um Object que é o
objeto lido do arquivo. Na linha 17 uma referência a um Object é declarada e
inicializada com null. Na linha 19 um objeto do tipo FileInputStream é declarado e
instanciado recebendo em seu construtor o objeto f. Na linha 20 um objeto do
tipo ObjectInputStream é declarado e instanciado recebendo como parâmetro o
objeto fis. Na linha 21 o método readObject é chamado. Esta chamada lê do
arquivo o objeto armazenado e armazena uma referência a este objeto na
referência o. Note que neste método duas exceções devem ser tratadas: uma
exceção do tipo IOException que irá ocorrer caso o arquivo não possa ser aberto,
e também uma exceção do tipo ClassNotFoundException que ocorre quando o
arquivo aberto não contiver nenhum objeto armazenado. Na linha 30 o objeto
lido do arquivo é retornado.
No método main da classe TestaSerializacao, o método gravaObjeto é chamado
recebendo como parâmetro o objeto do tipo File instanciado na linha 35 e o
objeto do tipo Pessoa instanciado na linha 24. Esta chamada faz com que o
objeto p seja serializado no arquivo “ArquivoPessoa.arq”.
Na linha 38 o método leObjeto é chamado recebendo como parâmetro o objeto f.
Este método lê do arquivo “ArquivoPessoa.arq” o objeto do tipo Pessoa e
armazena uma referência a este objeto na referência p2. O operador de cast é
necessário pois este método retorna uma referência a um objeto do tipo Object.
Como sabemos que o objeto armazenado no arquivo é do tipo Pessoa, realizamos
a conversão para que possamos chamar os métodos da classe Pessoa nas linhas
41 e 42.

Referências  Bibliográficas  
 
Deitel,  H.M.  Deitel,  P.J.  Java  Como  Programar.  6a  Edição,  Pearson,  São  Paulo  
2007.  
 
SERSON,  Roberto  Rubinstein.  Programação  Orientada  a  Objetos  com  Java.  Rio  
de  Janeiro,  Brasport,  2007.