Você está na página 1de 16

Zip com Java Neste artigo veremos como trabalhar com arquivos compactados em aplicaes Java.

Vamos conhecer o pacote java.util.zip, presente na plataforma Java SE desde a verso 1.1, que oferece recursos para a criao e manipulao de arquivos no formato Zip e Gzip, alm tambm do formato Jar, que , basicamente, um arquivo Zip que contem classes Java e outros recursos dentro dele. O artigo foi baseado no Java 5 (verso 1.5), mas deve funcionar com a verso 1.4.2. Viso geral da API no pacote java.util.zip que se encontram todas as classes da API padro do Java para ler e criar arquivos compactados. O pacote possui ao todo um conjunto de 14 classes, 2 excees e 1 interface. Deste conjunto, vamos apenas nos concentrar nas classes mais importantes e necessrias para o objetivo do artigo, que a criao e a extrao de arquivos compactados, portanto, vamos conhecer e estudar as classes ZipFile, ZipInputStream, ZipOutputStream e ZipEntry. As duas primeiras classes so as responsveis por lerem e extrairem o contedo dos arquivos Zip, no entanto, elas no so usadas em conjunto, mas sim separadamente. Ambas tm a mesma funo, porm com caractersticas diferentes. A classe seguinte, ZipOutputStream, responsvel por criar e gravar o Zip e a ltima representa uma entrada do Zip. Todo arquivo Zip composto de uma ou mais entradas. Cada entrada corresponde a um arquivo ou diretrio que originalmente foi compactado e armazenado no Zip e representado pela classe ZipEntry. Esta classe possui mtodos que permitem recuperar informaes de cada entrada armazenada no Zip. Veja a Tabela 1: Tabela 1. Mtodos para recuperar informaes da classe ZipEntry MTODO getName() getCompressedSize() getSize() getTime() isDirectory() getMethod() RETORNO Nome da entrada no zip. Tamanho do dado compactado da entrada ou -1 se desconhecido. Tamanho original da entrada ou -1 se desconhecido. Data e hora de modificao da entrada. Booleano que indica se um diretrio. Mtodo de compresso usado para armazenar a entrada. Pode ser comparado com as constantes STORED e DEFLATED da prpria classe.

Mais caractersticas das classes mencionadas e o modo de se recuperar um entrada do Zip sero apresentadas ao longo do artigo. Para facilitar o entendimento do artigo e simplificar o uso de termos, chamaremos os arquivos Zip apenas de Zip.

Listando o contedo do zip Ler e descompactar o contedo de um Zip apenas uma questo de abrir um stream para o arquivo e ler os dados retornados, da mesma maneira que se l arquivos comuns, no compactados. As classes ZipFile e ZipInputStream oferecem facilidades de leitura e extrao de arquivos Zip, sendo possvel recuperar cada um dos arquivos ou diretrios armazenados. Como j mencionado, elas no trabalham em conjunto, mas sim separadamente, pois ambas desempenham a mesma funcionalidade, porm com caractersticas diferentes. A classe ZipFile dispensa o uso explcito de streams para abrir o Zip e mantm um cache interno das entradas, assim, caso o Zip necessite ser aberto novamente, a operao ser mais rpida que no primeiro acesso. Entretanto o uso de ZipFile no aconselhvel caso o Zip seja alterado constantemente por outras aplicaes, pois o cache de entradas pode ficar desatualizado e causar inconsistncia. J a classe ZipInputStream usa streams para abrir o Zip e no mantm um cache

das entradas. O exemplo da Listagem 1 abre um Zip existente em disco usando a classe ZipFile, e exibe na sada padro o nome de todas as entradas (arquivos e diretrios) armazenadas nele. O Zip a ser aberto determinado ao instanciar ZipFile, cujo construtor pode receber um string com o caminho do Zip, ou um java.io.File. Listagem 1: ListagemZip.java

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16

package jm.zipper; import java.io.*; import java.util.zip.*; public class ListagemZip { public static void main(String[] args) throws IOException { String arquivo = "c:/teste.zip"; ZipFile zip = new ZipFile( arquivo ); Enumeration e = zip.entries(); while( e.hasMoreElements() ) { ZipEntry entrada = (ZipEntry) e.nextElement(); System.out.println( entrada.getName() ); } zip.close(); } }

As entradas do Zip so recuperadas com o mtodo entries() de ZipFile, que retorna um java.util.Enumeration permitindo navegar por uma coleo de objetos ZipEntry.

1 ZipFile zip = new ZipFile("c:/teste.zip"); 2 Enumeration entradas = zip.entries();


Com a atualizao da API para o Java 5, foi adotado o uso de Generics no retorno deste mtodo, que na verdade retorna um java.util.Enumeration, ou seja, ele define que o retorno um Enumeration com objetos ZipEntry ou seus subtipos. Este o nico caso de utilizao de Generics na API, portanto, todo o resto segue o padro de utilizao do Java 1.4. Ento, quem usa Java 5 pode fazer desta forma:

1 Enumeration<ZipEntry> entradas = zip.entries();


As entradas so recuperadas uma a uma, a cada iterao do lao while, com nextElement(), e o nome da entrada obtido com getName() e exibido na sada padro:

1 while(entradas.hasMoreElements()) { 2 ZipEntry entrada = (ZipEntry) entradas.nextElement(); 3 System.out.println(entrada.getName()); 4 }


A outra forma de trabalhar com o Zip, usando a classe ZipInputStream, est demonstrada na Listagem 2, que faz o mesmo que o primeiro exemplo - abre o Zip e lista as entradas na sada padro. Listagem 2: ListagemZip2.java

01 package jm.zipper; 02 import java.io.*;

03 import java.util.zip.*; 04 05 public class ListagemZip2 { 06 public static void main(String[] args) throws IOException { 07 String arquivo = "c:/teste.zip"; 08 FileInputStream fis = new FileInputStream( arquivo ); 09 ZipInputStream zis = new ZipInputStream( fis ); 10 ZipEntry entrada = null; 11 while( (entrada = zis.getNextEntry()) != null ) { 12 System.out.println( entrada.getName() ); 13 } 14 zis.close(); 15 fis.close(); 16 } 17 }
Inicialmente, abrimos um FileInputStream para o Zip. Em seguida criamos um objeto ZipInputStream, que recebe um InputStream em seu construtor. este objeto que l o Zip e devolve os dados nele armazenados. O mtodo getNextEntry() retorna um objeto do tipo ZipEntry, que representa uma entrada (arquivo ou diretrio) armazenada no Zip. Como no primeiro exemplo, a cada iterao do while, obtida a prxima entrada do Zip e exibido o seu nome na sada padro.

A aplicao de exemplo Para exemplificar a extrao e a criao de arquivos Zip mais completamente, criamos uma aplicao grfica, no estilo do aplicativo Winzip, porm mais simples, mostrada em execuo na Figura 1.

Figura 1: Aplicao de exemplo listando o contedo de um Zip. A aplicao usa uma classe principal, com mtodos que encapsulam as funcionalidades de manipulao de Zips, que criamos para simplificar as operaes e tambm poder ser reusada em outros casos e aplicaes. esta classe que descreveremos aqui. O restante das classes no ser apresentado, mas est disponvel para download. A Listagem 3 exibe o cdigo da classe jm.Zipper. No programa de exemplo foram usados os

recursos de ZipFile para a extrao do Zip, mas mostraremos paralelamente como utilizar ZipInputStream para o mesmo fim.

001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057

package jm.zipper; import java.io.*; import java.util.*; import java.util.zip.*; public class Zipper { public List listarEntradasZip( File arquivo ) throws ZipException, IOException List entradasDoZip = new ArrayList(); ZipFile zip = null; try { zip = new ZipFile( arquivo ); Enumeration e = zip.entries(); ZipEntry entrada = null; while( e.hasMoreElements() ) { entrada = (ZipEntry) e.nextElement(); entradasDoZip.add ( entrada ); } setArquivoZipAtual( arquivo ); } finally { if( zip != null ) { zip.close(); } } return entradasDoZip; } public void extrairZip( File diretorio ) throws ZipException, IOException { extrairZip( this.getArquivoZipAtual(), diretorio ); } public void extrairZip( File arquivoZip, File diretorio ) throws ZipException, ZipFile zip = null; File arquivo = null; InputStream is = null; OutputStream os = null; byte[] buffer = new byte[TAMANHO_BUFFER]; try { //cria diretrio informado, caso no exista if( !diretorio.exists() ) { diretorio.mkdirs(); } if( !diretorio.exists() || !diretorio.isDirectory() ) { throw new IOException("Informe um diretrio vlido"); } zip = new ZipFile( arquivoZip ); Enumeration e = zip.entries(); while( e.hasMoreElements() ) { ZipEntry entrada = (ZipEntry) e.nextElement(); arquivo = new File( diretorio, entrada.getName() ); //se for diretrio inexistente, cria a estrutura //e pula pra prxima entrada if( entrada.isDirectory() && !arquivo.exists() ) { arquivo.mkdirs(); continue; } //se a estrutura de diretrios no existe, cria

058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

if( !arquivo.getParentFile().exists() ) { arquivo.getParentFile().mkdirs(); } try { //l o arquivo do zip e grava em disco is = zip.getInputStream( entrada ); os = new FileOutputStream( arquivo ); int bytesLidos = 0; if( is == null ) { throw new ZipException("Erro ao ler a entrada do zip: "+entrada.getNa } while( (bytesLidos = is.read( buffer )) > 0 ) { os.write( buffer, 0, bytesLidos ); } } finally { if( is != null ) { try { is.close(); } catch( Exception ex ) {} } if( os != null ) { try { os.close(); } catch( Exception ex ) {} } } } } finally { if( zip != null ) { try { zip.close(); } catch( Exception e ) {} } } }

public List criarZip( File arquivoZip, File[] arquivos ) throws ZipException, I FileOutputStream fos = null; BufferedOutputStream bos = null; setArquivoZipAtual( null ); try { //adiciona a extenso .zip no arquivo, caso no exista if( !arquivoZip.getName().toLowerCase().endsWith(".zip") ) { arquivoZip = new File( arquivoZip.getAbsolutePath()+".zip" ); } fos = new FileOutputStream( arquivoZip ); bos = new BufferedOutputStream( fos, TAMANHO_BUFFER ); List listaEntradasZip = criarZip( bos, arquivos ); setArquivoZipAtual( arquivoZip ); return listaEntradasZip; } finally { if( bos != null ) { try { bos.close(); } catch( Exception e ) {} } if( fos != null ) { try { fos.close(); } catch( Exception e ) {}

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

} }

public List criarZip( OutputStream os, File[] arquivos ) throws ZipException, I if( arquivos == null || arquivos.length < 1 ) { throw new ZipException("Adicione ao menos um arquivo ou diretrio"); } List listaEntradasZip = new ArrayList(); ZipOutputStream zos = null; try { zos = new ZipOutputStream( os ); for( int i=0; i<arquivos.length; i++ ) { String caminhoInicial = arquivos[i].getParent(); List novasEntradas = adicionarArquivoNoZip( zos, arquivos[i], caminhoInic if( novasEntradas != null ) { listaEntradasZip.addAll( novasEntradas ); } } } finally { if( zos != null ) { try { zos.close(); } catch( Exception e ) {} } } return listaEntradasZip; }

private List adicionarArquivoNoZip( ZipOutputStream zos, File arquivo, String c List listaEntradasZip = new ArrayList(); FileInputStream fis = null; BufferedInputStream bis = null; byte buffer[] = new byte[TAMANHO_BUFFER]; try { //diretrios no so adicionados if( arquivo.isDirectory() ) { //recursivamente adiciona os arquivos dos diretrios abaixo File[] arquivos = arquivo.listFiles(); for( int i=0; i<arquivos.length; i++ ) { List novasEntradas = adicionarArquivoNoZip( zos, arquivos[i], caminhoIn if( novasEntradas != null ) { listaEntradasZip.addAll( novasEntradas ); } } return listaEntradasZip; } String caminhoEntradaZip = null; int idx = arquivo.getAbsolutePath().indexOf(caminhoInicial); if( idx >= 0 ) { //calcula os diretrios a partir do diretrio inicial //isso serve para no colocar uma entrada com o caminho completo caminhoEntradaZip = arquivo.getAbsolutePath().substring( idx+caminhoInici } ZipEntry entrada = new ZipEntry( caminhoEntradaZip ); zos.putNextEntry( entrada ); zos.setMethod( ZipOutputStream.DEFLATED ); fis = new FileInputStream( arquivo ); bis = new BufferedInputStream( fis, TAMANHO_BUFFER ); int bytesLidos = 0;

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 }

while((bytesLidos = bis.read(buffer, 0, TAMANHO_BUFFER)) != -1) { zos.write( buffer, 0, bytesLidos ); } listaEntradasZip.add( entrada ); } finally { if( bis != null ) { try { bis.close(); } catch( Exception e ) {} } if( fis != null ) { try { fis.close(); } catch( Exception e ) {} } } return listaEntradasZip; } public void fecharZip() { setArquivoZipAtual( null ); } public File getArquivoZipAtual() { return arquivoZipAtual; } private void setArquivoZipAtual(File arquivoZipAtual) { this.arquivoZipAtual = arquivoZipAtual; } private File arquivoZipAtual; private static final int TAMANHO_BUFFER = 2048; // 2 Kb

A Tabela 2 resume os mtodos pblicos dessa classe. Tabela 2. Mtodos pblicos da classe de exemplo Zipper MTODO listarEntradasZip( File ) DESCRIO Abre o arquivo zip informado e retorna um List de objetos ZipEntry. Cria um arquivo zip com nome e path informado no File fornecido, contendo os arquivos e diretrios no array de objetos File. Adiciona subdiretrios e arquivos, recursivamente. Sobrecarga de criarZip(), porm recebendo um stream (em vez de um File) para gravao dos bytes do zip. Extrai o zip informado no primeiro argumento para o diretrio informado no segundo. (Lembrando que objetos File podem representar tanto arquivos como diretrios). Extrai o ltimo zip utilizado pela classe Zipper, no diretrio informado como argumento.

criarZip( File, File[] )

criarZip(java.io.OutputStream, File[])

extrairZip( File, File ) extrairZip( File )

getArquivoZipAtual()

Retorna a referncia para o ltimo arquivo zip usado ou null se no houver um arquivo. Encerra as referncias para o ltimo zip em uso. usado no programa quando fechamos o arquivo aberto.

fecharZip()

A escolha do uso de ZipFile em vez de ZipInputStream na aplicao no foi com feita base em aspectos tcnicos ou performticos, mas foi meramente por um problema detectado durante o desenvolvimento do programa. Se voc tentar abrir um arquivo que no seja do formato ZIP com a classe ZipInputStream, nenhuma exceo lanada e nenhum byte lido. A classe ZipFile, ao contrrio, lana uma exceo caso um arquivo fora do padro seja aberto, o que, no nosso caso, importante saber, para que o programa fique consistente e no haja problemas quando o usurio tenta abrir outro tipo de arquivo.

Extrao Para explicar a extrao, tomaremos como base o mtodo extrairZip( File, File ). Inicialmente o mtodo verifica se existe o diretrio onde o zip ser extrado, seno o cria. Caso o diretrio especificado seja invlido, lanada uma exceo:

1 if( !diretorio.isDirectory() ) 2 throw new IOException("Informe um diretrio vlido"); 3 else if( !diretorio.exists() ) 4 diretorio.mkdirs();
Em seguida criado um objeto ZipFile, vinculado ao Zip em disco. A partir deste objeto, recuperamos as entradas do Zip com o mtodo entries():

1 zip = new ZipFile( arquivoZip ); 2 Enumeration e = zip.entries();


Ento extramos o contedo para cada entrada recuperada. Para isso, criamos um objeto File com o diretrio onde ele ser extrado e o nome do arquivo, baseado no nome da entrada. Se a entrada representar um diretrio inexistente, ento criamos o diretrio em disco e pulamos para a prxima entrada. Se o File representar um arquivo, verificamos se existe a estrutura de diretrios a qual ele pertence e ento a criamos, se no existir. Esse cuidado necessrio porque as entradas extradas do Zip podem conter, alm do nome, um caminho. Consulte o quadro "java.io.File e Streams" para mais detalhes sobre essas classes.

01 while( e.hasMoreElements() ) { 02 ZipEntry entrada = (ZipEntry) e.nextElement(); 03 arquivo = new File( diretorio, entrada.getName() ); 04 if( entrada.isDirectory() && !arquivo.exists() ) { 05 arquivo.mkdirs(); 06 continue; 07 } 08 if( !arquivo.getParentFile().exists() ) { 09 arquivo.getParentFile().mkdirs(); 10 } 11 ...
dentro do bloco try, logo em seguida ao trecho anterior, que acontece a extrao do arquivo. Para cada entrada, por meio do objeto de ZipFile, obtemos um InputStream, informando a

entrada a ser recuperada. E criamos um FileOutputStream para gravar o arquivo em disco. Repare que o mtodo getInputStream() recebe como argumento o ZipEntry que vai ser recuperado, ento o mtodo retorna o InputStream para e leitura dos dados da entrada especificada, permitindo, desta forma, a leitura aleatria das entradas do Zip.

1 is = zip.getInputStream( entrada ); 2 os = new FileOutputStream( arquivo );


No while feita a leitura dos dados provenientes do InputStream, que ento so gravados em disco, atravs do stream de sada. Ao final da gravao, fechamos os dois streams abertos, dentro do bloco finally. Finalizadas a extrao e a gravao em disco de todas as entradas, fechamos o zip com o mtodo close() de ZipFile. Extraindo com ZipInputStream A Listagem 4 demonstra como fazer a extrao com ZipInputStream. Com ZipInputStream, os dados podem ser lidos diretamente atravs do mtodo read(), o qual retorna apenas os dados da entrada atual. Veja que esta classe l o Zip seqencialmente, entrada a entrada. Para passar prxima entrada invocamos o mtodo getNextEntry(). Nesse exemplo abrimos um ZipInputStream vinculado a um Zip em disco (usando um BufferedOutputStream). Listagem 4: Extrao de zip com ZipInputStream

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22

String arquivo = "c:/teste.zip"; final int BUFFER = 2048; FileInputStream fis = new FileInputStream( arquivo ); BufferedInputStream bis = new BufferedInputStream( fis, BUFFER ); ZipInputStream zis = new ZipInputStream( bis ); ZipEntry entrada = null; while( (entrada = zis.getNextEntry()) != null ) { int bytesLidos = 0; byte dados[] = new byte[BUFFER]; //grava o arquivo em disco FileOutputStream fos = new FileOutputStream(entrada.getName()); BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER); while( (bytesLidos = zis.read(dados, 0, BUFFER)) != -1 ) { dest.write( dados, 0, bytesLidos ); } dest.flush(); dest.close(); fos.close(); } zis.close(); bis.close(); fis.close();

Criando um zip Ainda com base no programa de exemplo, vamos analisar como podemos criar arquivos Zip com Java. Relembrando, a Listagem 3 exibe a classe Zipper que foi criada para encapsular e facilitar o uso das funcionalidades da API de Zip no nosso programa de exemplo, que baseado no aplicativo Winzip, porm mais simples. O mtodo criarZip( File, File[] ) da classe Zipper usada pelo programa de exemplo para cria um Zip em disco, armazenando e compactando os arquivos e diretrios selecionados, incluindo toda a raiz de subdiretrios e arquivos. Este mtodo recebe dois argumentos: o File para o Zip a ser criado e um array de File que contm os arquivos e diretrios a serem compactados e armazenados no Zip. Acompanhando a Listagem 3, n o cdigo do mtodo, podemos ver o que ele faz. Primeiro verifica

se o arquivo informado possui a extenso .zip, seno cria um novo File para conter a extenso desejada, baseado no File informado.

1 if( !arquivoZip.getName().toLowerCase().endsWith(".zip") ) { 2 arquivoZip = new File( arquivoZip.getAbsolutePath()+".zip" ); 3 }


Depois do passo anterior criado um stream para o arquivo, que ser usado para gravar os bytes em disco.

1 fos = new FileOutputStream( arquivoZip ); 2 bos = new BufferedOutputStream( fos, 2048 );


No exatamente no mtodo atual que est a lgica de criao do Zip, mas sim na sua sobrecarga, criarZip( OutputStream, File[] ), que recebe o objeto de OutputStream para o Zip e o mesmo array de File recebido como argumento. Esta sobrecarga foi criada para dar maior usabilidade classe, pois utilizando esta opo podemos no s gravar o Zip em disco, mas tambm gravar os dados em qualquer stream de sada, como por exemplo no stream da resposta HTTP, sendo possvel enviar ao navegador web um Zip, sem a necessidade de grav-lo em disco. Mais tarde voltaremos a falar sobre gerao de Zip na web. Voltando criao do Zip, vamos olhar dentro do mtodo criarZip( OutputStream, File[] ), que onde o Zip realmente criado. A primeira instruo no mtodo verifica se o segundo argumento, o array de File, veio como null ou sem itens. Caso verdadeiro ele lana uma exceo informando o erro. Em seguida criada uma lista, com a classe ArrayList, com o nome da varivel listaEntradasZip, que vai armazenar todos os objetos ZipEntry que forem armazenados no Zip, que no final ser o retorno do mtodo. Esta uma informao a ser retornada apenas para no ter que se abrir e ler o Zip novamente para obter a lista de ZipEntry que o Zip criado contm. No caso da nossa aplicao de exemplo, depois de criado o Zip, ser exibida na tela uma lista das entradas armazenadas.

1 if( arquivos == null || arquivos.length < 1 ) { 2 throw new ZipException("Adicione ao menos um arquivo ou diretrio"); 3 } 4 List listaEntradasZip = new ArrayList();
Continuando no cdigo do mtodo chegamos ao primeiro ponto que interessa de fato na criao do Zip. por meio da classe ZipOutputStream que gravamos os dados que sero armazenados e compactados dentro do Zip. O construtor da classe recebe o OutputStream para onde sero gravados os bytes do Zip. O lao for a seguir percorre cada item do array de File recebido como argumento e, para cada File, pega o caminho onde se encontra o File e passa ao mtodo privado adicionarArquivoNoZip(), junto com o stream e o File a ser adicionado no Zip. dentro deste mtodo que o File ser gravado no stream do Zip. O mtodo foi criado para ser reusado, principalmente recursivamente, para incluir subdiretrios e outros arquivos existentes dentro de um diretrio informado.

1 zos = new ZipOutputStream( os ); 2 for( int i=0; i<arquivos.length; i++ ) { 3 String caminhoInicial = arquivos[i].getParent(); 4 List novasEntradas = adicionarArquivoNoZip( zos, arquivos[i], caminhoInicial ); 5 if( novasEntradas != null ) { 6 listaEntradasZip.addAll( novasEntradas ); 7 } 8 }
O mtodo adicionarArquivoNoZip() recebe trs argumentos: o ZipOutputStream, onde sero

gravados os arquivos no Zip, o File a ser gravado e o caminho onde se encontra este File. Dentro do mtodo, a primeira instruo verifica se o File informado um diretrio. Caso positivo, ento obtida a lista de arquivos e diretrios que existem dentro deste diretrio e atravs do lao for, cada File listado ser includo no Zip, atravs de uma chamada recursiva ao mtodo adicionarArquivoNoZip(). O retorno deste mtodo ser adicionado lista de entradas adicionadas, que vai compor a lista final completa das entradas do Zip.

01 if( arquivo.isDirectory() ) { 02 //recursivamente adiciona os arquivos dos diretrios abaixo 03 File[] arquivos = arquivo.listFiles(); 04 for( int i=0; i<arquivos.length; i++ ) { 05 List novasEntradas = adicionarArquivoNoZip( zos, arquivos[i], caminhoInicial ) 06 if( novasEntradas != null ) { 07 listaEntradasZip.addAll( novasEntradas ); 08 } 09 } 10 return listaEntradasZip; 11 }
Caso o File no seja um diretrio, o fluxo segue normalmente pelo mtodo, e o primeiro passo calcular o nome completo da entrada no Zip. Este passo foi considerado, pois no desejamos que a entrada seja adicionada ao zip com o seu caminho completo, por exemplo C:\arquivos\imagens\foto1.jpg, mas sim contendo apenas o caminho relativo ao local onde o arquivo foi originalmente selecionado, por exemplo imagens\foto1.jpg. Calculado este caminho, ento criado um ZipEntry que ser adicionado ao Zip atravs do mtodo putNextEntry() de ZipOutputStream. Quando se adiciona uma entrada a um Zip, deve-se fazer desta forma. Em seguida chamado o mtodo setMethod(), que define se a entrada deve ser adicionada de forma compactada ou apenas armazenada (sem compactao). Este mtodo aceita um argumento do tipo int, que pode ser representado por duas constantes da classe ZipOutpuStream: DEFLATED (compactada) e STORED (sem compactao). Seguindo no cdigo, criado um stream para ler o contedo do arquivo a ser adicionado ao Zip, que lido dentro do lao while e gravado ao ZipOutputStream, atravs do seu mtodo write(), que segue o padro dos streams de sada. Por fim a entrada adicionada lista de entradas e depois os streams de leitura do arquivo adicionado so fechados e a lista parcial de entradas adicionadas retornada.

01 02 03 04 05 06 07 08 09 10

ZipEntry entrada = new ZipEntry( caminhoEntradaZip ); zos.putNextEntry( entrada ); zos.setMethod( ZipOutputStream.DEFLATED ); fis = new FileInputStream( arquivo ); bis = new BufferedInputStream( fis, TAMANHO_BUFFER ); int bytesLidos = 0; while((bytesLidos = bis.read(buffer, 0, TAMANHO_BUFFER)) != -1) { zos.write( buffer, 0, bytesLidos ); } listaEntradasZip.add( entrada );

Neste ponto a execuo volta ao mtodo criarZip(), caso no seja uma chamada recursiva. Ao final, o objeto de ZipOutputStream fechado, aps a incluso de todas as entradas no Zip. E est finalizada a criao do zip. Resumindo a criao do Zip: primeiro criamos um ZipOutputStream para o OutputStream onde vamos gravar o Zip (seja em arquivo ou outro meio), depois, para cada entrada a ser adicionada ao Zip, criamos um ZipEntry com o nome (e caminho) da entrada, adicionamos a entrada ao ZipOutputStream com putNextEntry(), ento lemos os dados do arquivo a ser adicionado e gravamos no Zip com o mtodo write(). Exemplo:

1 ZipOutputStream zos = new ZipOutputStream( os );

2 3 4 5

ZipEntry entrada = new ZipEntry( "diretrio/arquivo.txt" ); zos.putNextEntry( entrada ); zos.write( bytesDoArquivoTxt ); zos.close();

Gerando zip na web Dois problemas recorrentes do protocolo HTTP na internet hoje podem ser solucionados com arquivos Zip. 1) A largura de banda limitada que no permite downloads mais rpidos pode ser, em parte, solucionado com a compactao do contedo de arquivos a serem baixados; 2) Outro problema que o protocolo HTTP no permite download de mais de um arquivo simultaneamente, ou seja, com apenas um request, e isso pode ser solucionado adicionando os vrios arquivos em um nico Zip e enviando este para o cliente. Nesta seo vamos demonstrar uma simples aplicao web, com uma pgina JSP que navega no sistema de arquivos (Imagem 2) da mquina em que se localiza o servidor web e permite fazer o download de um ou mais arquivos de uma s vez, recebido na forma de um Zip, via um Servlet. Figura 2: Tela do programa web que gera zip

O mais interessante desta aplicao que o Zip gerado dinamicamente e enviado ao cliente sem a necessidade de criar um arquivo temporrio ou intermedirio em disco, ou seja, tudo feito em memria e enviado ao cliente (gerao on-the-fly), evitando acesso ao disco. O cdigo do JSP que navega no sistema de arquivo est fora do escopo e no ser listado no artigo, mas est disponvel para download. O que nos interesse de fato o cdigo do Servlet, que recebe uma requisio com o caminho dos arquivos selecionados para download - atravs do parmetro HTTP "arquivo" - gera o Zip e o envia na resposta HTTP. A Listagem 6 exibe todo o cdigo da classe DownloadServlet, que um HttpServlet. Listagem 6: Servlet de download de ZIP

01 02 03 04 05 06

package jm.zipper.web; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import jm.zipper.Zipper;

07 08 public class DownloadServlet extends HttpServlet { 09 public void doGet(HttpServletRequest req, HttpServletResponse res) 10 throws ServletException, IOException { 11 doPost( req, res ); 12 } 13 14 protected void doPost(HttpServletRequest req, HttpServletResponse res) 15 throws ServletException, IOException { 16 String[] arquivos = req.getParameterValues("arquivo"); 17 if( arquivos == null || arquivos.length <= 0 ) { 18 req.setAttribute("msg","Selecione ao menos um arquivo"); 19 req.getRequestDispatcher("index.jsp").forward( req, res ); 20 return; 21 } 22 res.setContentType("application/octet-stream"); 23 res.setHeader("Content-Disposition", "attachment; filename=arquivos.zip"); 24 gerarZip( res.getOutputStream(), arquivos ); 25 } 26 27 private void gerarZip( OutputStream os, String[] nomesArquivos ) throws IOExcept 28 File[] arquivos = new File[ nomesArquivos.length ]; 29 for( int i=0; i<arquivos.length; i++ ) { 30 arquivos[i] = new File( nomesArquivos[i] ); 31 } 32 Zipper zipper = new Zipper(); 33 zipper.criarZip( os, arquivos ); 34 os.flush(); 35 } 36 }
O Servlet atende apenas a requisies GET e POST, pois implementa apenas os mtodos doGet() e doPost(). Dentro do mtodo obtemos um array de strings, que ser o caminho absoluto dos arquivos e diretrios selecionados no JSP. A partir deste array verificamos se foi selecionado pelo menos um item, seno, uma mensagem de erro enviada e exibida de volta no JSP.

1 String[] arquivos = req.getParameterValues("arquivo"); 2 if( arquivos == null || arquivos.length <= 0 ) { 3 req.setAttribute("msg","Selecione ao menos um arquivo"); 4 req.getRequestDispatcher("index.jsp").forward( req, res ); 5 return; 6 }
Caso haja sido feita a seleo, o programa faz duas coisas importantes no ambiente web. Primeiro define o contedo da resposta, ou seja, o content-type, que no caso definido como application/octet-stream. Depois definimos um cabealho (Content-Disposition) que diz ao navegador que estamos enviando um arquivo anexado (attachment) e que o navegador deve abrir a janela perguntando se o usurio deseja abrir ou salvar o arquivo enviado. Depois obtemos o stream para a resposta HTTP e ento chamamos o mtodo gerarZip() do prprio Servlet, que quem vai gerar o Zip, atravs da classe Zipper, que criamos para o programa de exemplo anterior.

1 2 3 4

res.setContentType("application/octet-stream"); res.setHeader("Content-Disposition", "attachment; filename=arquivos.zip"); OutputStream os = res.getOutputStream(); gerarZip( os, arquivos );

Repare que este mtodo gerarZip() recebe dois argumentos: o stream para a resposta HTTP e o array com os arquivos selecionados. Neste mtodo criamos um array de File, a partir do array de string, que ser um dos argumentos do mtodo criarZip() da classe Zipper. Recorde que vimos o mtodo criarZip() na seo anterior do artigo e vimos que este mtodo gera um Zip diretamente em um OutputStream ao invs de em um arquivo em disco. O que ocorre que gravamos os bytes do Zip diretamente na resposta HTTP que ser recebido pelo navegador, que reconhecer o arquivo e dar a opo de abrir ou salvar o arquivo recebido. Por fim feito um flush() no stream para efetivar o envio dos bytes ao cliente. O arquivo disponvel para download no site contm um arquivo WAR (jm-zipper.war) dentro do diretrio deploy, que pode ser instalado em qualquer container web ou J2EE disponvel. Coloque este WAR no local correto para o seu container (no caso do Tomcat, copie o WAR para o diretrio webapps) e o inicie. Abra o seu navegador preferido e aponte para http://localhost:8080/jmzipper/. Caso o seu servidor esteja configurado em outra mquina ou porta, altere os valores necessrios.

Trabalhando com GZIP Alternativamente ao formato ZIP, podemos criar e manipular arquivos GZIP, porm este ltimo formato no permite armazenamento de mltiplos arquivos, mas apenas de um arquivo por vez. Para mais detalhes sobre as diferenas entre os formatos Zip e Gzip, consulte o quadro "ZIP versus GZIP". Para criar e extrair contedo do GZIP usamos as classes GZIPInputStream e GZIPOutputStream, respectivamente. A Listagem 7 mostra um programa que cria um arquivo GZIP e depois extrai o contedo do mesmo GZIP criado. Perceba que a criao e extrao meramente uma questo de abrir um stream e gravar ou obter os dados. Listagem 7: Manipulando arquivos GZIP

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

package jm.zipper; import java.io.*; import java.util.zip.*; public class ExemploGZip { public static void main(String[] args) throws Exception { int TAMANHO_BUFFER = 2048; //2 KBytes byte[] dados = new byte[TAMANHO_BUFFER]; File arquivo = new File("c:/teste.txt"); File arquivo2 = new File("c:/teste2.txt"); File arquivoGzip = new File("c:/teste.gz"); //cria o GZIP OutputStream os = new FileOutputStream( arquivoGzip ); GZIPOutputStream gos = new GZIPOutputStream( os ); InputStream is = new FileInputStream( arquivo ); int bytesLidos = 0; while( (bytesLidos = is.read( dados, 0, TAMANHO_BUFFER )) > 0 ) { gos.write( dados, 0, bytesLidos ); } is.close(); gos.close(); os.close(); //extrai o GZIP InputStream is2 = new FileInputStream( arquivoGzip ); GZIPInputStream gis = new GZIPInputStream( is2 ); OutputStream os2 = new FileOutputStream( arquivo2 ); bytesLidos = 0; while( (bytesLidos = gis.read( dados, 0, TAMANHO_BUFFER )) > 0 ) { os2.write( dados, 0, bytesLidos ); }

34 35 36 37 38 }

os2.close(); gis.close(); is2.close(); }

ZIP versus GZIP Os usurios do Windows esto familiarizados com o formato ZIP, que serve tanto para armazenar arquivos, como para comprimir dados. Os usurios Linux/Unix usam tambm o formato GZIP, que apenas comprime dados, no servido como arquivador, pois este formato no permite mais de um arquivo armazenado nele. Geralmente usurios Linux usam duas ferramentas: o tar, para gerar um arquivo que arquiva (armazena) diversos outros arquivos e o gzip para compactar o arquivo tar gerado, criando arquivos com a extenso no padro tar.gz.

java.io.File e Streams A classe java.io.File uma representao abstrata para o caminho de um arquivo ou diretrio, independente do sistema operacional ou sistema de arquivos utilizado. Esta classe possui muitos mtodos teis para a obteno de informaes do arquivo em disco, verificar se ele existe, se arquivo ou diretrio, criar a estrutura de diretrios representada, criar um arquivo vazio em disco, listar os diretrios e arquivos etc. Para mais detalhes da classe, consulte a documentao em: http://java.sun.com/j2se/1.4.2/docs/api/java/io/File.html. Os streams (ou fluxos) so a base da comunicao dos dados. Eles so como os dutos que transportam os bytes de um lado para o outro. Stream um conceito genrico para o transporte de dados, que utilizado em diversas necessidades como acesso a arquivo em disco, comunicao de rede via sockets etc. Basicamente existem dois tipos de streams: os de entrada, chamados de input stream; e os de sada, chamados output stream. Os streams de entrada servem para ler ou receber dados oriundos de alguma fonte, j os de sada, consequentemente, servem para enviar ou gravar dados para outro destino. Em comum, as classes que implementam os streams de entrada fornecem o mtodo read() para leitura dos dados e as classes que implementam os stream de sada fornecem o mtodo write() para gravar os dados. Para estar dentro do padro de streams do Java, toda classe que um stream de entrada deve implementar a interface java.io.InputStream, direta ou indiretamente. O mesmo vale para as classes de stream de sada, que devem implementar a interface java.io.OutputStream. As classes BufferedInputStream e BufferedOutputStream implementam o conceito de buffer para a leitura e gravao de bytes via streams. O uso dessas classes torna os acesso mais performticos, pois trabalham com os bytes de dados em memria e evita o acesso direto a todo instante ao disco, por exemplo. Mais informaes, consulte o tutorial oficial da Sun: http://java.sun.com/docs/books/tutorial/essential/TOC.html#io

Concluses O artigo demonstrou atravs de exemplos a utilizao do pacote java.util.zip para a leitura, extrao e criao de arquivos ZIP, alm de uma aplicao de exemplo no estilo Winzip e como podemos gerar arquivos zip on-the-fly na web. Para finalizar, foi demonstrado tambm como se trabalhar com arquivos GZIP. Agora voc tem o conhecimento necessrio para trabalhar com compactao Zip de arquivos, que poder ajudar muito a enriquecer suas aplicaes. interessante que o leitor baixe o contedo disponvel para download, pois assim voc pode verificar todos os fontes, alm de abrir o projeto na IDE Eclipse (verso utilizada 3.1) ou uma outra de sua preferncia - ser necessrio criar um novo projeto para outra IDE. Dentro do diretrio deploy disponibilizado o JAR com os binrios do artigo e o programa de exemplo. Para iniciar o programa, d um duplo-clique com o mouse no JAR ou execute a seguinte linha de comando:

java -jar jm-zipper.jar