Trabalhando Com Arquivos Do Java IO Ao NIO 2

7
Trabalhando com arquivos do Java IO ao NIO 2 Postado dia 10/08/2011 por Mário Amaral em Inovação , Java 17 Durante anos, trabalhar com arquivos em Java foi muito trabalhoso. Precisávamos conhecer e interagir com diversas classes do pacote java.io a fim de realizar tarefas simples, como ler um arquivo ou simplesmente copiá-los de uma pasta para outra. Alguns projetos open source sugiram para facilitar essas tarefas, como o Commons-IO da Apache. Conforme a linguagem foi evoluindo, estas tarefas foram se tornando mais fáceis de serem realizadas sem a necessidade de bibliotecas externas, graças aos recursos incorporados na API padrão. Vamos acompanhar como foi parte dessa evolução, usando o exemplo de copiar uma árvore de diretórios. A seguir, vemos a árvore de diretórios que iremos copiar:

Transcript of Trabalhando Com Arquivos Do Java IO Ao NIO 2

Page 1: Trabalhando Com Arquivos Do Java IO Ao NIO 2

Trabalhando com arquivos do Java IO ao NIO 2Postado dia 10/08/2011 por Mário Amaral em Inovação, Java 17

Durante anos, trabalhar com arquivos em Java foi muito trabalhoso.

Precisávamos conhecer e interagir com diversas classes do pacote java.io a

fim de realizar tarefas simples, como ler um arquivo ou simplesmente copiá-los

de uma pasta para outra. Alguns projetos open source sugiram para facilitar

essas tarefas, como o Commons-IO da Apache.

Conforme a linguagem foi evoluindo, estas tarefas foram se tornando mais

fáceis de serem realizadas sem a necessidade de bibliotecas externas, graças

aos recursos incorporados na API padrão. Vamos acompanhar como foi parte

dessa evolução, usando o exemplo de copiar uma árvore de diretórios.

A seguir, vemos a árvore de diretórios que iremos copiar:

É uma arvore de arquivos comum. Antes de copiar a árvore inteira, vamos ver

o código para copiar um arquivo sozinho:

// Arquivos que iremos copiarFile origem = new File("/home/caelum/Java/vraptor.zip");File destino = new File("/home/caelum/Java/vraptor-copia.zip");

Page 2: Trabalhando Com Arquivos Do Java IO Ao NIO 2

 // abrimos os streams para leitura/escritaFileInputStream fis = new FileInputStream(origem);FileOutputStream fos = new FileOutputStream(destino); // Obtém os canais por onde lemos/escrevemos nos arquivosFileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();

 // copia todos o conteúdo do canal de entrada para o canal de saídaoutChannel.transferFrom(inChannel, 0, inChannel.size());

 // fecha os streams/channels usados...

Cada FileInputStream/FileOutputStream recebe um objeto do

tipo File, que representa o local do arquivo no disco. Após isso criamos

canais por onde copiaremos o conteudo do arquivo de origem para o de

destino. Usar FileChannels para realizar a cópia pode ser bem mais

eficiente do que se lessemos/escrevessemos utilizando os

antigos streams diretamente. Essa API não-blocante foi adicionada ao Java

1.4, em 2001.

Para copiar a estrutura de diretórios é muito mais simples, basta chamar o

método mkdirs da classeFile, que cria o diretório e toda a árvore necessária:File novoDiretorio = new File("/home/caelum-copia/Java");novoDiretorio.mkdirs(); //cria o diretório "caelum-copia" e o diretório "Java" dentro da pasta /home/

Mas isso só cria o diretório vazio, sem nenhum arquivo dentro. Para copiar o

conteúdo, primeiro precisamos saber o que há dentro dele, para isso usamos o

método File.listFiles, que nos retorna um array com todas as entradas

do diretório, tanto arquivos quanto subdiretórios:File raiz = new File("/home/mario/Documents/caelum/Java");File[] files = raiz.listFiles();

 for (File file : files) {  // copia os arquivos / subdiretórios}

Precisamos repetir esta operação para cada subdiretório encontrado. A melhor

solução neste caso é realizar uma invocação recursiva para copiar o conteúdo

de cada subdiretório encontrado.

public void copiarArquivos(File origem, File destino) {    // verifica se estamos tentando copiar um diretório    if (origem.isDirectory()) {      File[] files = origem.listFiles();

       for (File file : files) {        // invocacao recursiva de cada item de dentro do diretorio

Page 3: Trabalhando Com Arquivos Do Java IO Ao NIO 2

        copiaArquivos(file, destinoEquivalente);      }    } else {      // copia o arquivo usando os streams/channels    }}

Juntando tudo que vimos até aqui, temos um método de cópia de diretórios.

Não é um código simples, pois além de usar recursão para realizar a cópia dos

subdiretórios, ainda envolve várias diferentes classes na cópia dos arquivos.

Também deveria ser refatorado para 2 ou 3 métodos.

Existem diversos outros problemas com as antigas classes do java.io.

Podemos pegar como exemplo a classe File, que tem alguns dos métodos

para manipulação de arquivos, mas a maioria desses métodos não lança

exceptions quando falham, e podem funcionar diferentemente em cada sistema

operacional.

Vamos tentar deletar um diretório que possui arquivos dentro. O código segue

abaixo:

File root = new File("/home/caelum");boolean deletou = root.delete(); // retorna false...

Sabemos que o diretório não foi removido pelo retorno do método, mas é dificil

saber o motivo, se é um problema de acesso, se o diretório que não está vazio,

se não existe, etc., ficando para o programador a responsabilidade de descobrir

o que aconteceu.

A partir do Java 7, foi introduzida uma nova API no pacote java.nio.file, visando

reduzir a complexidade das operações realizadas em arquivos. A nova

classe Files possui diversos métodos utilitários para manipular arquivos,

lançando exceptions quando ocorre algum erro na operação, possibilitando ao

desenvolvedor entender de forma rápida o motivo da falha. Os métodos

de Filesutilizam a classe Path, uma nova interface utilizada para representar

entradas de arquivos/diretórios no sistema operacional.

O código para deletar o diretório ficaria da seguinte maneira:

Path rootPath = Paths.get("/home/caelum");Files.delete(rootPath); // o que acontece aqui?

Page 4: Trabalhando Com Arquivos Do Java IO Ao NIO 2

A classe Paths é uma fábrica de Path. O resultado desse código, é uma

exception:Exception in thread "main" java.nio.file.DirectoryNotEmptyException: /home/caelum

Vamos alterar nosso método copiaArquivos para utilizar as novas classes

do Java 7:public void copiarArquivos(Path origem, Path destino) throws IOException {  // se é um diretório, tentamos criar. se já existir, não tem problema.  if(Files.isDirectory(origem)){    Files.createDirectories(destino);

     // listamos todas as entradas do diretório    DirectoryStream<Path> entradas = Files.newDirectoryStream(origem);

     for (Path entrada : entradas) {      // para cada entrada, achamos o arquivo equivalente dentro de cada arvore      Path novaOrigem = origem.resolve(entrada.getFileName());      Path novoDestino = destino.resolve(entrada.getFileName());

       // invoca o metodo de maneira recursiva      copiarArquivos(novaOrigem, novoDestino);    }  } else {    // copiamos o arquivo    Files.copy(origem, destino);  }}

A estrutura do algoritmo é a mesma utilizada anteriormente, mas agora o

número de classes envolvidas é muito menor. Para copiar um arquivo, apenas

chamamos o método Files.copy, não precisando mais trabalhar

com streams e channels nesses casos.

Ainda é trabalhoso percorrer um diretório inteiro dessa maneira, mas podemos

utilizar a nova interface FileVisitor, uma implementação do pattern Visitor,

para percorrer toda a árvore de diretórios de maneira mais simples. Os

métodos dessa interface são

preVisitDirectory Chamado antes de entrar em um diretório

postVisitDirectory Chamado depois de passar por todos

arquivos/subdiretórios de um diretótio

visitFile Chamado para cada arquivo

visitFileFailed Chamado se acontecer alguma exception em

algum dos outros métodos

Todos os métodos retornam FileVisitResult, um enum com os as opções

para continuar ou não percorendo a árvore de diretórios.

Page 5: Trabalhando Com Arquivos Do Java IO Ao NIO 2

Vamos então implementar um FileVisitor para copiar nosso diretório. Nem

sempre precisamos de todos os métodos da interface; neste caso, podemos

estender a classe SimpleFileVisitor e apenas sobreescrever os métodos

que realmente necessitamos:public class CopiadorDeArquivos extends SimpleFileVisitor {  private Path origem;  private Path destino;   public CopiadorDeArquivos(Path origem, Path destino) {    this.origem = origem;    this.destino = destino;  }

   public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)          throws IOException {    copiaPath(dir);    return FileVisitResult.CONTINUE;  }

   public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)          throws IOException {    copiaPath(file);    return FileVisitResult.CONTINUE;  }

   private void copiaPath(Path entrada) throws IOException {    // encontra o caminho equivalente na árvore de cópia    Path novoDiretorio = destino.resolve(origem.relativize(entrada));    Files.copy(entrada, novoDiretorio);  }}

Na classe acima, usamos apenas dois métodos, o visitFile, chamado para

copiar cada arquivo existente na árvore, e o preVisitDirectory, chamado

para criar o diretório. Para executar a cópia, faríamos:Path origem = Paths.get("/home/caelum");Path destino = Paths.get("/home/caelum-copia");

 Files.walkFileTree(origem, new CopiadorDeArquivos(origem, destino));

O método walkFileTree recebe o diretório que será percorido, e

um FileVisitor, que irá passar por todos os arquivos.

Apesar de ter tornado alguma operações mais simples, essa nova API de

arquivos possui mais métodos estáticos e a maioria recebe uma instância

de Path, separando comportamento e dados. Talvez com o futuro Java 8, as

closures e defender methods tornarão a interface Path mais amigável, sem

depender tanto de métodos estáticos.

Page 6: Trabalhando Com Arquivos Do Java IO Ao NIO 2

Além das classes apresentadas, ainda tivemos outros acréscimos na API de

NIO, como leitura/escrita de streams de forma assíncrona, serviços

para monitorar alterações em diretórios e métodos para lermetadados de

arquivos de maneira simplificadas. Essas novidades são conhecidas como

NIO2. Com o passar das versões, o Java traz cada vez mais para suas APIs

internas ferramentas necessárias para o dia a dia.