Input/Output em Java

Neste artigo vamos abordar uma matéria que, a nosso ver, é bastante interessante e importante no mundo da programação: o input/output, neste caso aplicado a Java.

Iremos começar com manipulação de ficheiros e directorias. Vamos ver a seguinte classe Java, onde são demostradas as principais funções da classe File, que é a utilizada para realizar a manipulação. Em seguida vamos explicar o que cada linha faz.

import java.io.*;

public class JavaFile {
   public static void main(String args[]) {
      File file = new File("C:\\file.txt");

      System.out.println( file.getName() ); 

      file.setReadOnly();

      System.out.println( file.isHidden() );
      System.out.println( file.canRead() );
      System.out.println( file.canWrite() );

      file.renameTo( new File("C:\\Ficheiro.txt") ); 

      try {
         if( !file.exists() ) {
            file.createNewFile();
         }
      }
      catch(Exception e){
         System.out.println(e.getMessage());
      }

      if( file.isDirectory() ) {
         System.out.println("Directoria");
      }
      else if( file.isFile() ){
         System.out.println("Ficheiro");
      }

      File [] roots = file.listRoots( );

      for (int i = 0; i < roots.length; i++) {
         System.out.println (roots[i] );
      }

      file.delete();
   }
}

Como podemos ver na linha 5 é instanciado um objecto File que representa o caminho (path) para um possível local do sistema operativo, é bom lembrar que apenas representa um ficheiro ou directoria, não pressupondo que o caminho exista realmente. Neste caso o caminho é o C:\\file.txt, que aponta para o ficheiro file.txt na directoria C:. Também poderia apontar apenas para uma directoria e não para uma ficheiro, como é o caso.

Na linha 7 podemos ver o método getName() que permite obter o nome do ficheiro ou directoria representada pelo File.

Esta classe permite também dar atributos a ficheiros ou directorias, como é o caso do método setReadOnly(), que dá ao ficheiro ou directoria o atributo de apenas poder ser lido e não escrito, tal como esta representado na linha 9.

A classe File permite também verificar atributos e, para isso, podemos usar os métodos isHidden() que verifica se o ficheiro ou directoria se encontra oculto(a) (linha 11), canRead() que verifica se é possível ler o ficheiro ou directoria (linha 12) e o método canWrite() que verifica se é possível escrever no ficheiro ou directoria (linha 13).

O método renameTo() na linha 15 permite renomear um ficheiro ou directoria, mas para além disso permite também mover ficheiros e directorias, bastando para isso dar um caminho diferente no novo nome a dar, o que não é o caso neste exemplo.

Os métodos exists() e createNewFile() (linha 18 e linha 19), são dois métodos muito importantes na manipulação de ficheiros e directorias. O método exists() permite verificar se o ficheiro ou directoria representados no File existe. O método createNewFile() cria um novo ficheiro com o caminho representado. Neste caso iria criar o ficheiro file.txt na directoria C:.

Embora aqui não presente, também temos os métodos mkdir() e mkdirs() que têm a mesma funcionalidade que o método createNewFile(), mas neste caso é criada uma directoria. A diferença do mkdir() para o mkdirs() é basicamente que o método mkdir() apenas cria uma directoria num caminho já existem, ou seja, por exemplo, o seguinte caminho C:\Programas\ o mkdir() poderia criar directorias dentro da directoria Programas apenas e só se a referida directoria já existisse previamente. Já o mkdirs() permite criar toda a árvore de directorias, mesmo se esta não existisse.

Nas linhas 26 e 29 podemos ver os métodos isDirectory() e isFile() que verificam respectivamente se o caminho dado é um directorio ou um ficheiro.

Na linha 33 temos o método listRoots(). Trata-se de um método bastante útil, nomeadamente em sistemas Windows, visto que ele devolve um array com todas a drives ou raízes do sistema operativo, por exemplo A:, C:, D:, E:, etc… Já em sistemas GNU/Linux o conteúdo do array será apenas /, visto ser a raiz do sistema.

Para terminhar esta parte do artigo, temos na linha 39 o método delete(), que como o proprio nome indica, permite eliminar o ficheiro ou directoria representado pelo File.

Depois de terminado o estudo sobre a manipulação de ficheiros, vamos agora passar à escrita dos mesmos, observando o seguinte código.

import java.io.*;

public class FileWrite {
   public static void main(String args[]) {
      try{
         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("C:\\file.txt",true)));
 
         out.println("Java Input/Output");
          out.println(System.getProperty("os.name").toString());
          out.println(System.getProperty("user.name").toString());
         out.println("Fim");
         out.println();
         out.close();
      }
      catch(IOException e){
         System.out.println(e.getMessage());
      }
   }
}

Após executar esta aplicação será criado um ficheiro de texo em C:\\ (nos sistemas Windows, para outros sistemas operativos bastará mudar o destino), com conteúdo semelhante a este:

Java Input/Output
Windows XP
Magician
Fim

Vamos agora analisar o código de forma a compreender o objectivo de cada linha.

Na linha 6 podemos ver a o objecto out a ser instanciado. Esta é uma instanciação um pouco complexa, mas tem a sua razão de ser. Segundo alguns teste já feitos esta instanciação é a forma
mais simples e com melhor rendimento de escrever num ficheiro. Vamos agora atentar em alguns detalhes. Na última parte da instância new FileWriter("C:\\file.txt",true) podemos ver que para além de termos como argumento o nome do ficheiro, temos também um argumento boolean. Este é sem duvida um argumento muito importante pois quando colocado true diz ao programa que deve escrever no ficheiro, com a particularidade de não apagar nenhum do seu conteúdo anterior, ou seja, o programa não escreverá por cima do que já la se encontra. No caso de se colocar como argumento um boolean false, ou apenas o nome do ficheiro, o programa cada vez que for executado vai reescrever o ficheiro a partir do início, apagando toda a informação já la contida.

Após executar o programa duas vezes com o argumento true, o conteúdo do ficheiro irá aparecer duas vezes. O conteúdo do ficheiro só aparecerá uma vez, após executar o programa duas vezes, com o argumento false ou sem o argumento.

Na linha 8, bem como na 9, 10, 11 e 12, podemos ver a utilização do método println() sobre o objecto out. Este método vai permitir escrever uma linha no ficheiro. Existem outros métodos como o print() e write() com funcionalidade idênticas, embora menos utilizados ao nível da escrita de ficheiros de texto.

Por fim temos o método close() que irá “fechar” o ficheiro, algo que deve ser sempre executado assim que se termina a escrita num ficheiro. É assim terminada a ligação entre o ficheiro e o programa. É, portanto, um passo fulcral, visto que caso não executado, poderá deitar a perder o conteúdo do ficheiro.

Agora que já sabemos como escrever em ficheiros apenas falta a leitura dos mesmos. Para isso vamos começar por observar o seguinte código.

import java.io.*;

public class FileRead {
   public static void main(String args[]) {
      try {
         BufferedReader leitor = new BufferedReader(new FileReader("C:\\file.txt"));
         String linha = "";
         linha = leitor.readLine();
         while(linha != null) {
            System.out.println(linha);
            linha = leitor.readLine();
         }
         leitor.close();
      }
      catch(IOException e){
         System.out.println(e.getMessage());
      }
   }
}

De uma forma resumida, o que este código vai fazer é simplesmente ir ao ficheiro file.txt, ler linha a linha e imprimir na consola. Como podemos ver na linha 6 é criado um objecto BufferedReader que, através de um objecto FileReader, irá aceder ao ficheiro file.txt. Na linha 7 vamos criar a variável linha, do tipo String, inicializando-a como string vazia (""). Em seguida na, linha 8, vamos ler a primeira linha do ficheiro e vamos colocar o conteúdo dessa linha na variável linha, sob forma de String.

Nas linhas 9–12 é feito um ciclo para ler todas as linhas do ficheiro, cada vez que o ciclo da uma volta, é impreso o valor da linha anterior e lido a linha seguinte, até não existir mais linhas no ficheiro. Finda a leitura,  a ligação ao ficheiro é terminada. O resultado final será exactamente o conteúdo do ficheiro.

Após estas três partes do artigo o leitor já conseguirá realizar as principais funções de IO em Java. Existe um sem numero de outras formas de fazer o que foi aqui demonstrado, bem como um sem número de outras coisas de se pode fazer com Java IO.

Publicado na edição 5 (PDF) da Revista PROGRAMAR.