No artigo anterior vimos como representar um arquivo através de um objeto em java, agora veremos como gravar informações dentro dele.

Em java temos duas formas de tratar arquivos, como um fluxo baseado em bytes armazenando seus valores em seu formato binário, por exemplo se formos armazenar o número 25 em arquivo em um fluxo de bytes será armazenado em sua forma binária que seria “00011001“. Ou podemos armazenar o valor 25 como um fluxo baseado em caracteres onde cada um dos caracteres é armazenado utilizando o valor unicode dele, ou seja, o caractere 2 é representado pelo valor 50 na tabela unicode e que em binário ficaria “0000000000110010” e o caractere 5 é representado por 53 que em binário ficaria “0000000000110101“, então para armazenar o valor 25 em um arquivo baseado em caracteres seria armazenado “00000000001100100000000000110101” a diferença dos dois não é simplesmente o tamanho, o primeiro 25 armazenado em binário é um inteiro e pode ser utilizado para um calculo sem nenhuma conversão, o 25 armazenado como caractere foi armazenado como string e para todos os efeitos é um texto, tanto que podemos abrir um arquivo baseado em caractere em qualquer editor de texto e visualizar as informações, o mesmo não é possível utilizando um arquivo baseado e bytes que necessita de um programa especial que converta suas informações para um formato legível para pessoas normais.

Para lermos e gravarmos dados em um arquivo utilizamos uma combinação de classes do pacote java.io como por exemplo, para gravarmos e lermos em um fluxo de caracteres (FileWriter e FileReader) e para lermos e gravarmos em um fluxo de bytes(FileInputStream e FileOutputStream).

Vamos começar então gravando em um fluxo de caracteres:

File arquivo = new File("teste.txt");
try( FileWriter fw = new FileWriter(arquivo) ){
    fw.write('2');
    fw.write("25");
    fw.flush();
}catch(IOException ex){
  ex.printStackTrace();
}

Como vemos no exemplo acima criamos um objeto do tipo FileWriter que aceita em seu construtor(linha 2) um objeto do tipo File para escrever neste arquivo. O principal método fornecido por FileWrite é o método write que é sobrecarregado para receber um char ou uma String ou um char[] para gravá-lo no arquivo. Lembrando que se você colocar um literal inteiro como parâmetro ele irá interpretá-lo como sendo um char, ou seja, ele irá gravar o caractere unicode correspondente ao número informado.

Na linha 5 chamamos o método flush para forçar que todas que por ventura ainda estejam no buffer sejam gravadas no arquivo, sem esse método pode ser que as ultimas informações inseridas no arquivo não sejam gravadas. Neste caso não foi necessário fechar o arquivo com o método close já que estamos utilizando a nova sintaxe do try na linha 2 que fecha automaticamente o recurso, ou seja o arquivo.

Agora lendo de um fluxo de caracteres:

File arquivo = new File("teste.txt");
try( FileReader fr = new FileReader(arquivo) ){
    int  c =   fr.read();
    while( c != -1){
        System.out.print( (char) c );
        c =  fr.read();
    }
}catch(IOException ex){
  ex.printStackTrace();
}

A classe FileReader nos fornece o método read que lê um único caractere do arquivo e retorna o número inteiro de seu código na tabela unicode, ou se for o final do arquivo ele retornará -1.

Outra opção que temos é o método sobrecarregado read(char[] c) que recebe um array de char no qual será inserido os caracteres lidos.

File arquivo = new File("teste.txt");
 try(FileReader fr = new FileReader(arquivo)){
   char[]  c =  new char[4];
   fr.read(c);
   System.out.print( c );
 }catch(IOException ex){
   ex.printStackTrace();
 }

Vamos trabalhar agora com os fluxo baseados em bytes:

File arquivo = new File("teste.bin");
try( OutputStream os = new FileOutputStream(arquivo) ){
  byte[] b = {50,51,52,53};
  String string = "Rodrigo Aramburu";
  os.write( 53 );
  os.write( b );
  os.write( string.getBytes() );
  os.flush();
}catch(IOException ex){
  ex.printStackTrace();
}
 
try( InputStream is = new FileInputStream(arquivo) ){
  int content;
  while ( (content = is.read() ) != -1) {
    System.out.println( content +" - "+ ( (char) content) );
  }
}catch(IOException ex){
  ex.printStackTrace();
}

Como podemos ver no exemplo acima utilizamos a classe FileOutputStream para gravar bytes em um arquivo e FileInputStream para ler. Para escrever nos é fornecido o método write que pode receber um byte(linha 5) ou um array de bytes como na lina 6 e 7 do exemplos. Na linha 7 chamamos o método getBytes da classe String que converte os caracteres da String em bytes, lembrando que quando estamos trabalhando com OutputStream todos os dados devem ser convertidos para bytes para serem gravados.

Na linha 15 utilizamos um laço while para percorrer o arquivo até o fim, para cada passada utilizamos o método read que lê um byte e retorna na forma de um int. O método read retorna -1 se chagar no final do arquivo.

Trabalhar gravando e lendo de arquivos, byte por byte ou caractere por caractere pode ser bastante trabalhoso sem falar que não é muito eficiente, lembrando que gravar em um arquivo é uma das tarefas mais lentas do computador já que se esta enviando informações para um dispositivo bastante lento em comparação com a memória. Então quando vamos gravar informações em um arquivo o melhor a fazer é gravar uma grande quantidade de uma vez para otimizar essa operação, para isso podemos utilizar as classes BufferedOutputStream e BufferedInputStream que como o proprio nome diz “buferizam” antes de gravar.

File arquivo = new File("arquivo.txt");
try( FileOutputStream fo = new FileOutputStream( arquivo ) ){
    BufferedOutputStream bos = new BufferedOutputStream(fo);             
    bos.write( "teste".getBytes());
    bos.write("\n".getBytes() );//inserindo um caractere de nova linha
    bos.write( "teste2".getBytes());
    bos.flush();  
 
}catch(IOException ex){
    ex.printStackTrace();
}
 
try( FileInputStream fi = new FileInputStream(arquivo)){
    BufferedInputStream bis = new BufferedInputStream(fi);
 
    int content;
    while( ( content = bis.read() ) != -1){
        System.out.println( content  + " - " + ( (char) content) );
    }
}catch(IOException ex){
    ex.printStackTrace();
}

Como podemos ver as classes BufferedOutputStream e BufferedInputStream são utilizadas praticamente do mesmo modo que as classes OutputStream e InputStream sendo basicamente um encapsulamento destas classes.

Como temos classes para buferizar o acesso de stream baseados em bytes, temos também classes para stream baseados em caracteres, as classes BufferedWriter e BufferedReader.

File arquivo = new File("arquivo2.txt");
try( FileWriter fw = new FileWriter( arquivo ) ){
  BufferedWriter bw = new BufferedWriter(fw);             
  bw.write( "teste" );
  bw.newLine();
  bw.write( "teste2");
  bw.flush();  
}catch(IOException ex){
  ex.printStackTrace();
}
 
try( FileReader fr = new FileReader(arquivo)){
  BufferedReader br = new BufferedReader(fr);
  String content;
  while( ( content = br.readLine() ) != null){
    System.out.println( content );
  }
}catch(IOException ex){
  ex.printStackTrace();
}

Outra classe muito interessante para escrever caracteres em um arquivo é PrintWriter pois possui vários métodos que permitem uma melhor formatação dos dados escritos, lembrando que a classe PrintWriter é uma subclasse de Writer então os dados serão escritos na forma de caracteres.

File arquivo = new File("print.txt");
try(PrintWriter pw = new PrintWriter(arquivo) ){
  pw.println(true);
  pw.println(10);
  pw.println(10.20);
  pw.println("teste");
  pw.printf("String: %s | Double: %.2f | Inteiro: %5d " , "teste", 10f , 200);
}catch(IOException ex){
  ex.printStackTrace();
}

Saida:

true
10
10.2
teste
String: teste | Double: 10,00 | Inteiro:   200

A classe PrintWriter possui o método println sobrecarregado para aceitar todos os tipos de dados e o e método printf que insere uma String formatada utilizando argumentos passados como os demais argumentos do método, sendo:

  • %s é inserida uma string no lugar.
  • %.2f é inserido um número em ponto flutuante, sendo o “.2” para ser colocado duas casas decimais
  • %5d é inserido uma inteiro ocupando no total 5 casas

Outra opção interessante para ler dados de um arquivo via InputStream é a classe java.util.Scanner bem conhecida para ler dados do teclado, mas em vez de passar o System.in como parâmetro do construtor passamos o FileInputStream, como é bom o polimorfismo 🙂 .

File arquivo = new File("teste.bin");
try( InputStream in = new FileInputStream(arquivo) ){
  Scanner scan = new Scanner(in);
  while( scan.hasNext() ){
    System.out.println( scan.nextLine() );
  }
}catch(IOException ex){
  ex.printStackTrace();
}

Documentação das classes:

That’s All Folks!