Ir para conteúdo
Faça parte da equipe! (2024) ×

Compressão e Descompressão de dados em Java


SubCash
 Compartilhar

Posts Recomendados

Neste artigo veremos como trabalhar com arquivos compactados em aplicações Java. Vamos conhecer o pacote java.util.zip, presente na plataforma Java SE desde a versão 1.1, que oferece recursos para a criação e manipulação de arquivos no formato Zip e Gzip, além também do formato Jar, que é, basicamente, um arquivo Zip que contem classes Java e outros recursos dentro dele.

 

O artigo foi baseado no Java 5 (versão 1.5), mas deve funcionar com a versão 1.4.2.

 

 

Visão geral da API

 

 

É no pacote java.util.zip que se encontram todas as classes da API padrão do Java para ler e criar arquivos compactados.

 

O pacote possui ao todo um conjunto de 14 classes, 2 exceções e 1 interface. Deste conjunto, vamos apenas nos concentrar nas classes mais importantes e necessárias para o objetivo do artigo, que é a criação e a extração de arquivos compactados, portanto, vamos conhecer e estudar as classes ZipFile, ZipInputStream, ZipOutputStream e ZipEntry.

 

As duas primeiras classes são as responsáveis por lerem e extrairem o conteúdo dos arquivos Zip, no entanto, elas não são usadas em conjunto, mas sim separadamente. Ambas têm a mesma função, porém com características diferentes. A classe seguinte, ZipOutputStream, é responsável 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 diretório que originalmente foi compactado e armazenado no Zip e é representado pela classe ZipEntry. Esta classe possui métodos que permitem recuperar informações de cada entrada armazenada no Zip. Veja a Tabela 1:

 

Tabela 1. Métodos para recuperar informações da classe ZipEntry

 

MÉTODO RETORNO

 

 

  • getName() - Nome da entrada no zip.
  • getCompressedSize() - Tamanho do dado compactado da entrada ou -1 se desconhecido.
  • getSize() - Tamanho original da entrada ou -1 se desconhecido.
  • getTime() - Data e hora de modificação da entrada.
  • isDirectory() - Booleano que indica se é um diretório.
  • getMethod() - Método de compressão usado para armazenar a entrada. Pode ser comparado com as constantes STORED e DEFLATED da própria classe.

 

 

Mais características das classes mencionadas e o modo de se recuperar um entrada do Zip serão 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 conteúdo do Zip

 

 

Ler e descompactar o conteúdo de um Zip é apenas uma questão de abrir um stream para o arquivo e ler os dados retornados, da mesma maneira que se lê arquivos comuns, não compactados.

 

As classes ZipFile e ZipInputStream oferecem facilidades de leitura e extração de arquivos Zip, sendo possível recuperar cada um dos arquivos ou diretórios armazenados. Como já mencionado, elas não trabalham em conjunto, mas sim separadamente, pois ambas desempenham a mesma funcionalidade, porém com características diferentes.

 

A classe ZipFile dispensa o uso explícito de streams para abrir o Zip e mantém um cache interno das entradas, assim, caso o Zip necessite ser aberto novamente, a operação será mais rápida que no primeiro acesso. Entretanto o uso de ZipFile não é aconselhável caso o Zip seja alterado constantemente por outras aplicações, pois o cache de entradas pode ficar desatualizado e causar inconsistência. Já a classe ZipInputStream usa streams para abrir o Zip e não mantém um cache das entradas.

 

O exemplo da Listagem 1 abre um Zip existente em disco usando a classe ZipFile, e exibe na saída padrão o nome de todas as entradas (arquivos e diretórios) 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 package jm.zipper;

02 import java.io.*;

03 import java.util.zip.*;

04

05 public class ListagemZip {

06 public static void main(String[] args) throws IOException {

07 String arquivo = "c:/teste.zip";

08 ZipFile zip = new ZipFile( arquivo );

09 Enumeration e = zip.entries();

10 while( e.hasMoreElements() ) {

11 ZipEntry entrada = (ZipEntry) e.nextElement();

12 System.out.println( entrada.getName() );

13 }

14 zip.close();

15 }

16 }

 

 

As entradas do Zip são recuperadas com o método entries() de ZipFile, que retorna um java.util.Enumeration permitindo navegar por uma coleção de objetos ZipEntry.

 

1 ZipFile zip = new ZipFile("c:/teste.zip");

2 Enumeration entradas = zip.entries();

 

 

Com a atualização da API para o Java 5, foi adotado o uso de Generics no retorno deste método, 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 utilização de Generics na API, portanto, todo o resto segue o padrão de utilização do Java 1.4. Então, quem usa Java 5 pode fazer desta forma:

 

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

 

 

As entradas são recuperadas uma a uma, a cada iteração do laço while, com nextElement(), e o nome da entrada é obtido com getName() e exibido na saída padrão:

 

 

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 saída padrão.

 

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 método getNextEntry() retorna um objeto do tipo ZipEntry, que representa uma entrada (arquivo ou diretório) armazenada no Zip. Como no primeiro exemplo, a cada iteração do while, é obtida a próxima entrada do Zip e exibido o seu nome na saída padrão.

 

A aplicação de exemplo

 

 

Para exemplificar a extração e a criação de arquivos Zip mais completamente, criamos uma aplicação gráfica, no estilo do aplicativo Winzip, porém mais simples, mostrada em execução na Figura 1.

 

Figura 1: Aplicação de exemplo listando o conteúdo de um Zip.

111-001.gif

 

A aplicação usa uma classe principal, com métodos que encapsulam as funcionalidades de manipulação de Zips, que criamos para simplificar as operações e também poder ser reusada em outros casos e aplicações. É esta classe que descreveremos aqui. O restante das classes não será apresentado, mas está disponível para download.

 

A Listagem 3 exibe o código da classe jm.Zipper. No programa de exemplo foram usados os recursos de ZipFile para a extração do Zip, mas mostraremos paralelamente como utilizar ZipInputStream para o mesmo fim.

 

001 package jm.zipper;

002 import java.io.*;

003 import java.util.*;

004 import java.util.zip.*;

005

006 public class Zipper {

007 public List listarEntradasZip( File arquivo ) throws ZipException, IOException {

008 List entradasDoZip = new ArrayList();

009 ZipFile zip = null;

010 try {

011 zip = new ZipFile( arquivo );

012 Enumeration e = zip.entries();

013 ZipEntry entrada = null;

014 while( e.hasMoreElements() ) {

015 entrada = (ZipEntry) e.nextElement();

016 entradasDoZip.add ( entrada );

017 }

018 setArquivoZipAtual( arquivo );

019 }

020 finally {

021 if( zip != null ) {

022 zip.close();

023 }

024 }

025 return entradasDoZip;

026 }

027

028 public void extrairZip( File diretorio ) throws ZipException, IOException {

029 extrairZip( this.getArquivoZipAtual(), diretorio );

030 }

031

032 public void extrairZip( File arquivoZip, File diretorio ) throws ZipException, IOException {

033 ZipFile zip = null;

034 File arquivo = null;

035 InputStream is = null;

036 OutputStream os = null;

037 byte[] buffer = new byte[TAMANHO_BUFFER];

038 try {

039 //cria diretório informado, caso não exista

040 if( !diretorio.exists() ) {

041 diretorio.mkdirs();

042 }

043 if( !diretorio.exists() || !diretorio.isDirectory() ) {

044 throw new IOException("Informe um diretório válido");

045 }

046 zip = new ZipFile( arquivoZip );

047 Enumeration e = zip.entries();

048 while( e.hasMoreElements() ) {

049 ZipEntry entrada = (ZipEntry) e.nextElement();

050 arquivo = new File( diretorio, entrada.getName() );

051 //se for diretório inexistente, cria a estrutura

052 //e pula pra próxima entrada

053 if( entrada.isDirectory() && !arquivo.exists() ) {

054 arquivo.mkdirs();

055 continue;

056 }

057 //se a estrutura de diretórios não existe, cria

058 if( !arquivo.getParentFile().exists() ) {

059 arquivo.getParentFile().mkdirs();

060 }

061 try {

062 //lê o arquivo do zip e grava em disco

063 is = zip.getInputStream( entrada );

064 os = new FileOutputStream( arquivo );

065 int bytesLidos = 0;

066 if( is == null ) {

067 throw new ZipException("Erro ao ler a entrada do zip: "+entrada.getName());

068 }

069 while( (bytesLidos = is.read( buffer )) > 0 ) {

070 os.write( buffer, 0, bytesLidos );

071 }

072 } finally {

073 if( is != null ) {

074 try {

075 is.close();

076 } catch( Exception ex ) {}

077 }

078 if( os != null ) {

079 try {

080 os.close();

081 } catch( Exception ex ) {}

082 }

083 }

084 }

085 } finally {

086 if( zip != null ) {

087 try {

088 zip.close();

089 } catch( Exception e ) {}

090 }

091 }

092 }

093

094 public List criarZip( File arquivoZip, File[] arquivos ) throws ZipException, IOException {

095 FileOutputStream fos = null;

096 BufferedOutputStream bos = null;

097 setArquivoZipAtual( null );

098 try {

099 //adiciona a extensão .zip no arquivo, caso não exista

100 if( !arquivoZip.getName().toLowerCase().endsWith(".zip") ) {

101 arquivoZip = new File( arquivoZip.getAbsolutePath()+".zip" );

102 }

103 fos = new FileOutputStream( arquivoZip );

104 bos = new BufferedOutputStream( fos, TAMANHO_BUFFER );

105 List listaEntradasZip = criarZip( bos, arquivos );

106 setArquivoZipAtual( arquivoZip );

107 return listaEntradasZip;

108 }

109 finally {

110 if( bos != null ) {

111 try {

112 bos.close();

113 } catch( Exception e ) {}

114 }

115 if( fos != null ) {

116 try {

117 fos.close();

118 } catch( Exception e ) {}

119 }

120 }

121 }

122

123 public List criarZip( OutputStream os, File[] arquivos ) throws ZipException, IOException {

124 if( arquivos == null || arquivos.length < 1 ) {

125 throw new ZipException("Adicione ao menos um arquivo ou diretório");

126 }

127 List listaEntradasZip = new ArrayList();

128 ZipOutputStream zos = null;

129 try {

130 zos = new ZipOutputStream( os );

131 for( int i=0; i<arquivos.length; i++ ) {

132 String caminhoInicial = arquivos.getParent();

133 List novasEntradas = adicionarArquivoNoZip( zos, arquivos, caminhoInicial );

134 if( novasEntradas != null ) {

135 listaEntradasZip.addAll( novasEntradas );

136 }

137 }

138 }

139 finally {

140 if( zos != null ) {

141 try {

142 zos.close();

143 } catch( Exception e ) {}

144 }

145 }

146 return listaEntradasZip;

147 }

148

149 private List adicionarArquivoNoZip( ZipOutputStream zos, File arquivo, String caminhoInicial ) throws IOException {

150 List listaEntradasZip = new ArrayList();

151 FileInputStream fis = null;

152 BufferedInputStream bis = null;

153 byte buffer[] = new byte[TAMANHO_BUFFER];

154 try {

155 //diretórios não são adicionados

156 if( arquivo.isDirectory() ) {

157 //recursivamente adiciona os arquivos dos diretórios abaixo

158 File[] arquivos = arquivo.listFiles();

159 for( int i=0; i<arquivos.length; i++ ) {

160 List novasEntradas = adicionarArquivoNoZip( zos, arquivos, caminhoInicial );

161 if( novasEntradas != null ) {

162 listaEntradasZip.addAll( novasEntradas );

163 }

164 }

165 return listaEntradasZip;

166 }

167 String caminhoEntradaZip = null;

168 int idx = arquivo.getAbsolutePath().indexOf(caminhoInicial);

169 if( idx >= 0 ) {

170 //calcula os diretórios a partir do diretório inicial

171 //isso serve para não colocar uma entrada com o caminho completo

172 caminhoEntradaZip = arquivo.getAbsolutePath().substring( idx+caminhoInicial.length()+1 );

173 }

174 ZipEntry entrada = new ZipEntry( caminhoEntradaZip );

175 zos.putNextEntry( entrada );

176 zos.setMethod( ZipOutputStream.DEFLATED );

177 fis = new FileInputStream( arquivo );

178 bis = new BufferedInputStream( fis, TAMANHO_BUFFER );

179 int bytesLidos = 0;

180 while((bytesLidos = bis.read(buffer, 0, TAMANHO_BUFFER)) != -1) {

181 zos.write( buffer, 0, bytesLidos );

182 }

183 listaEntradasZip.add( entrada );

184 }

185 finally {

186 if( bis != null ) {

187 try {

188 bis.close();

189 } catch( Exception e ) {}

190 }

191 if( fis != null ) {

192 try {

193 fis.close();

194 } catch( Exception e ) {}

195 }

196 }

197 return listaEntradasZip;

198 }

199

200 public void fecharZip() {

201 setArquivoZipAtual( null );

202 }

203

204 public File getArquivoZipAtual() {

205 return arquivoZipAtual;

206 }

207

208 private void setArquivoZipAtual(File arquivoZipAtual) {

209 this.arquivoZipAtual = arquivoZipAtual;

210 }

211

212 private File arquivoZipAtual;

213 private static final int TAMANHO_BUFFER = 2048; // 2 Kb

214 }

 

 

 

A Tabela 2 resume os métodos públicos dessa classe.

 

Tabela 2. Métodos públicos da classe de exemplo Zipper

 

MÉTODO DESCRIÇÃO

 

 

  • listarEntradasZip( File ) - Abre o arquivo zip informado e retorna um List de objetos ZipEntry.
  • criarZip( File, File[] ) - Cria um arquivo zip com nome e path informado no File fornecido, contendo os arquivos e diretórios no array de objetos File. Adiciona subdiretórios e arquivos, recursivamente.
  • criarZip(java.io.OutputStream, File[]) - Sobrecarga de criarZip(), porém recebendo um stream (em vez de um File) para gravação dos bytes do zip.
  • extrairZip( File, File ) - Extrai o zip informado no primeiro argumento para o diretório informado no segundo. (Lembrando que objetos File podem representar tanto arquivos como diretórios).
  • extrairZip( File ) - Extrai o último zip utilizado pela classe Zipper, no diretório informado como argumento.
  • getArquivoZipAtual() - Retorna a referência para o último arquivo zip usado ou null se não houver um arquivo.
  • fecharZip() - Encerra as referências para o último zip em uso. É usado no programa quando fechamos o arquivo aberto.

 

 

A escolha do uso de ZipFile em vez de ZipInputStream na aplicação não foi com feita base em aspectos técnicos ou performáticos, mas foi meramente por um problema detectado durante o desenvolvimento do programa. Se você tentar abrir um arquivo que não seja do formato ZIP com a classe ZipInputStream, nenhuma exceção é lançada e nenhum byte é lido. A classe ZipFile, ao contrário, lança uma exceção caso um arquivo fora do padrão seja aberto, o que, no nosso caso, é importante saber, para que o programa fique consistente e não haja problemas quando o usuário tenta abrir outro tipo de arquivo.

 

 

Extração

 

 

Para explicar a extração, tomaremos como base o método extrairZip( File, File ). Inicialmente o método verifica se existe o diretório onde o zip será extraído, senão o cria. Caso o diretório especificado seja inválido, é lançada uma exceção:

 

1 if( !diretorio.isDirectory() )

2 throw new IOException("Informe um diretório válido");

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 método entries():

 

1 zip = new ZipFile( arquivoZip );

2 Enumeration e = zip.entries();

 

 

Então extraímos o conteúdo para cada entrada recuperada. Para isso, criamos um objeto File com o diretório onde ele será extraído e o nome do arquivo, baseado no nome da entrada.

 

Se a entrada representar um diretório inexistente, então criamos o diretório em disco e pulamos para a próxima entrada. Se o File representar um arquivo, verificamos se existe a estrutura de diretórios a qual ele pertence e então a criamos, se não existir. Esse cuidado é necessário porque as entradas extraídas do Zip podem conter, além 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 extração 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 método getInputStream() recebe como argumento o ZipEntry que vai ser recuperado, então o método retorna o InputStream para e leitura dos dados da entrada especificada, permitindo, desta forma, a leitura aleatória 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 então são gravados em disco, através do stream de saída. Ao final da gravação, fechamos os dois streams abertos, dentro do bloco finally. Finalizadas a extração e a gravação em disco de todas as entradas, fechamos o zip com o método close() de ZipFile.

 

Extraindo com ZipInputStream

 

 

A Listagem 4 demonstra como fazer a extração com ZipInputStream. Com ZipInputStream, os dados podem ser lidos diretamente através do método read(), o qual retorna apenas os dados da entrada atual. Veja que esta classe lê o Zip seqüencialmente, entrada a entrada. Para passar à próxima entrada invocamos o método getNextEntry(). Nesse exemplo abrimos um ZipInputStream vinculado a um Zip em disco (usando um BufferedOutputStream).

 

Listagem 4: Extração de zip com ZipInputStream

 

01 String arquivo = "c:/teste.zip";

02 final int BUFFER = 2048;

03 FileInputStream fis = new FileInputStream( arquivo );

04 BufferedInputStream bis = new BufferedInputStream( fis, BUFFER );

05 ZipInputStream zis = new ZipInputStream( bis );

06 ZipEntry entrada = null;

07 while( (entrada = zis.getNextEntry()) != null ) {

08 int bytesLidos = 0;

09 byte dados[] = new byte[bUFFER];

10 //grava o arquivo em disco

11 FileOutputStream fos = new FileOutputStream(entrada.getName());

12 BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);

13 while( (bytesLidos = zis.read(dados, 0, BUFFER)) != -1 ) {

14 dest.write( dados, 0, bytesLidos );

15 }

16 dest.flush();

17 dest.close();

18 fos.close();

19 }

20 zis.close();

21 bis.close();

22 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, porém mais simples.

 

O método criarZip( File, File[] ) da classe Zipper é usada pelo programa de exemplo para cria um Zip em disco, armazenando e compactando os arquivos e diretórios selecionados, incluindo toda a raiz de subdiretórios e arquivos. Este método recebe dois argumentos: o File para o Zip a ser criado e um array de File que contém os arquivos e diretórios a serem compactados e armazenados no Zip.

 

Acompanhando a Listagem 3, no código do método, podemos ver o que ele faz. Primeiro verifica se o arquivo informado possui a extensão .zip, senão cria um novo File para conter a extensão 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 );

 

 

Não é exatamente no método atual que está a lógica de criação 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 opção podemos não só gravar o Zip em disco, mas também gravar os dados em qualquer stream de saída, como por exemplo no stream da resposta HTTP, sendo possível enviar ao navegador web um Zip, sem a necessidade de gravá-lo em disco. Mais tarde voltaremos a falar sobre geração de Zip na web.

 

Voltando à criação do Zip, vamos olhar dentro do método criarZip( OutputStream, File[] ), que é onde o Zip é realmente criado. A primeira instrução no método verifica se o segundo argumento, o array de File, veio como null ou sem itens. Caso verdadeiro ele lança uma exceção informando o erro. Em seguida é criada uma lista, com a classe ArrayList, com o nome da variável listaEntradasZip, que vai armazenar todos os objetos ZipEntry que forem armazenados no Zip, que no final será o retorno do método.

 

Esta é uma informação a ser retornada apenas para não ter que se abrir e ler o Zip novamente para obter a lista de ZipEntry que o Zip criado contém. No caso da nossa aplicação 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 diretório");

3 }

4 List listaEntradasZip = new ArrayList();

 

 

Continuando no código do método chegamos ao primeiro ponto que interessa de fato na criação do Zip.

 

É por meio da classe ZipOutputStream que gravamos os dados que serão armazenados e compactados dentro do Zip.

 

O construtor da classe recebe o OutputStream para onde serão gravados os bytes do Zip. O laço 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 método privado adicionarArquivoNoZip(), junto com o stream e o File a ser adicionado no Zip. É dentro deste método que o File será gravado no stream do Zip. O método foi criado para ser reusado, principalmente recursivamente, para incluir subdiretórios e outros arquivos existentes dentro de um diretório informado.

 

1 zos = new ZipOutputStream( os );

2 for( int i=0; i<arquivos.length; i++ ) {

3 String caminhoInicial = arquivos.getParent();

4 List novasEntradas = adicionarArquivoNoZip( zos, arquivos, caminhoInicial );

5 if( novasEntradas != null ) {

6 listaEntradasZip.addAll( novasEntradas );

7 }

8 }

 

 

O método adicionarArquivoNoZip() recebe três argumentos: o ZipOutputStream, onde serão gravados os arquivos no Zip, o File a ser gravado e o caminho onde se encontra este File.

 

Dentro do método, a primeira instrução verifica se o File informado é um diretório. Caso positivo, então é obtida a lista de arquivos e diretórios que existem dentro deste diretório e através do laço for, cada File listado será incluído no Zip, através de uma chamada recursiva ao método adicionarArquivoNoZip(). O retorno deste método 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 diretórios abaixo

03 File[] arquivos = arquivo.listFiles();

04 for( int i=0; i<arquivos.length; i++ ) {

05 List novasEntradas = adicionarArquivoNoZip( zos, arquivos, caminhoInicial );

06 if( novasEntradas != null ) {

07 listaEntradasZip.addAll( novasEntradas );

08 }

09 }

10 return listaEntradasZip;

11 }

 

 

Caso o File não seja um diretório, o fluxo segue normalmente pelo método, e o primeiro passo é calcular o nome completo da entrada no Zip.

 

Este passo foi considerado, pois não 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, então é criado um ZipEntry que será adicionado ao Zip através do método putNextEntry() de ZipOutputStream.

 

Quando se adiciona uma entrada a um Zip, deve-se fazer desta forma. Em seguida é chamado o método setMethod(), que define se a entrada deve ser adicionada de forma compactada ou apenas armazenada (sem compactação). Este método aceita um argumento do tipo int, que pode ser representado por duas constantes da classe ZipOutpuStream: DEFLATED (compactada) e STORED (sem compactação).

 

Seguindo no código, é criado um stream para ler o conteúdo do arquivo a ser adicionado ao Zip, que é lido dentro do laço while e gravado ao ZipOutputStream, através do seu método write(), que segue o padrão dos streams de saída.

 

Por fim a entrada é adicionada à lista de entradas e depois os streams de leitura do arquivo adicionado são fechados e a lista parcial de entradas adicionadas é retornada.

 

01 ZipEntry entrada = new ZipEntry( caminhoEntradaZip );

02 zos.putNextEntry( entrada );

03 zos.setMethod( ZipOutputStream.DEFLATED );

04 fis = new FileInputStream( arquivo );

05 bis = new BufferedInputStream( fis, TAMANHO_BUFFER );

06 int bytesLidos = 0;

07 while((bytesLidos = bis.read(buffer, 0, TAMANHO_BUFFER)) != -1) {

08 zos.write( buffer, 0, bytesLidos );

09 }

10 listaEntradasZip.add( entrada );

 

 

Neste ponto a execução volta ao método criarZip(), caso não seja uma chamada recursiva.

 

Ao final, o objeto de ZipOutputStream é fechado, após a inclusão de todas as entradas no Zip. E está finalizada a criação do zip.

 

Resumindo a criação 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(), então lemos os dados do arquivo a ser adicionado e gravamos no Zip com o método write().

 

Exemplo:

 

1 ZipOutputStream zos = new ZipOutputStream( os );

2 ZipEntry entrada = new ZipEntry( "diretório/arquivo.txt" );

3 zos.putNextEntry( entrada );

4 zos.write( bytesDoArquivoTxt );

5 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 não permite downloads mais rápidos pode ser, em parte, solucionado com a compactação do conteúdo de arquivos a serem baixados;

2) Outro problema é que o protocolo HTTP não permite download de mais de um arquivo simultaneamente, ou seja, com apenas um request, e isso pode ser solucionado adicionando os vários arquivos em um único Zip e enviando este para o cliente.

 

Nesta seção vamos demonstrar uma simples aplicação web, com uma página JSP que navega no sistema de arquivos (Imagem 2) da máquina 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

111-002.gif

 

O mais interessante desta aplicação é que o Zip é gerado dinamicamente e enviado ao cliente sem a necessidade de criar um arquivo temporário ou intermediário em disco, ou seja, tudo é feito em memória e enviado ao cliente (geração on-the-fly), evitando acesso ao disco.

 

O código do JSP que navega no sistema de arquivo está fora do escopo e não será listado no artigo, mas está disponível para download. O que nos interesse de fato é o código do Servlet, que recebe uma requisição com o caminho dos arquivos selecionados para download - através do parâmetro HTTP "arquivo" - gera o Zip e o envia na resposta HTTP.

 

A Listagem 6 exibe todo o código da classe DownloadServlet, que é um HttpServlet.

 

Listagem 6: Servlet de download de ZIP

 

01 package jm.zipper.web;

02 import java.io.*;

03 import javax.servlet.*;

04 import javax.servlet.http.*;

05

06 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 IOException {

28 File[] arquivos = new File[ nomesArquivos.length ];

29 for( int i=0; i<arquivos.length; i++ ) {

30 arquivos = new File( nomesArquivos );

31 }

32 Zipper zipper = new Zipper();

33 zipper.criarZip( os, arquivos );

34 os.flush();

35 }

36 }

 

 

O Servlet atende apenas a requisições GET e POST, pois implementa apenas os métodos doGet() e doPost(). Dentro do método obtemos um array de strings, que será o caminho absoluto dos arquivos e diretórios selecionados no JSP. A partir deste array verificamos se foi selecionado pelo menos um item, senão, 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 seleção, o programa faz duas coisas importantes no ambiente web.

 

Primeiro define o conteúdo da resposta, ou seja, o content-type, que no caso é definido como application/octet-stream. Depois definimos um cabeçalho (Content-Disposition) que diz ao navegador que estamos enviando um arquivo anexado (attachment) e que o navegador deve abrir a janela perguntando se o usuário deseja abrir ou salvar o arquivo enviado.

 

Depois obtemos o stream para a resposta HTTP e então chamamos o método gerarZip() do próprio Servlet, que é quem vai gerar o Zip, através da classe Zipper, que criamos para o programa de exemplo anterior.

 

1 res.setContentType("application/octet-stream");

2 res.setHeader("Content-Disposition", "attachment; filename=arquivos.zip");

3 OutputStream os = res.getOutputStream();

4 gerarZip( os, arquivos );

 

 

Repare que este método gerarZip() recebe dois argumentos: o stream para a resposta HTTP e o array com os arquivos selecionados. Neste método criamos um array de File, a partir do array de string, que será um dos argumentos do método criarZip() da classe Zipper.

 

Recorde que vimos o método criarZip() na seção anterior do artigo e vimos que este método gera um Zip diretamente em um OutputStream ao invés 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 opção de abrir ou salvar o arquivo recebido. Por fim é feito um flush() no stream para efetivar o envio dos bytes ao cliente.

 

O arquivo disponível para download no site contém um arquivo WAR (jm-zipper.war) dentro do diretório deploy, que pode ser instalado em qualquer container web ou J2EE disponível.

 

Coloque este WAR no local correto para o seu container (no caso do Tomcat, copie o WAR para o diretório webapps) e o inicie. Abra o seu navegador preferido e aponte para http://localhost:8080/jm-zipper/.

 

Caso o seu servidor esteja configurado em outra máquina ou porta, altere os valores necessários.

 

Trabalhando com GZIP

 

 

Alternativamente ao formato ZIP, podemos criar e manipular arquivos GZIP, porém este último formato não permite armazenamento de múltiplos arquivos, mas apenas de um arquivo por vez.

 

Para mais detalhes sobre as diferenças entre os formatos Zip e Gzip, consulte o quadro "ZIP versus GZIP".

 

Para criar e extrair conteúdo do GZIP usamos as classes GZIPInputStream e GZIPOutputStream, respectivamente.

 

A Listagem 7 mostra um programa que cria um arquivo GZIP e depois extrai o conteúdo do mesmo GZIP criado. Perceba que a criação e extração é meramente uma questão de abrir um stream e gravar ou obter os dados.

 

Listagem 7: Manipulando arquivos GZIP

 

01 package jm.zipper;

02 import java.io.*;

03 import java.util.zip.*;

04

05 public class ExemploGZip {

06 public static void main(String[] args) throws Exception {

07 int TAMANHO_BUFFER = 2048; //2 KBytes

08 byte[] dados = new byte[TAMANHO_BUFFER];

09

10 File arquivo = new File("c:/teste.txt");

11 File arquivo2 = new File("c:/teste2.txt");

12 File arquivoGzip = new File("c:/teste.gz");

13

14 //cria o GZIP

15 OutputStream os = new FileOutputStream( arquivoGzip );

16 GZIPOutputStream gos = new GZIPOutputStream( os );

17 InputStream is = new FileInputStream( arquivo );

18 int bytesLidos = 0;

19 while( (bytesLidos = is.read( dados, 0, TAMANHO_BUFFER )) > 0 ) {

20 gos.write( dados, 0, bytesLidos );

21 }

22 is.close();

23 gos.close();

24 os.close();

25

26 //extrai o GZIP

27 InputStream is2 = new FileInputStream( arquivoGzip );

28 GZIPInputStream gis = new GZIPInputStream( is2 );

29 OutputStream os2 = new FileOutputStream( arquivo2 );

30 bytesLidos = 0;

31 while( (bytesLidos = gis.read( dados, 0, TAMANHO_BUFFER )) > 0 ) {

32 os2.write( dados, 0, bytesLidos );

33 }

34 os2.close();

35 gis.close();

36 is2.close();

37 }

38 }

 

 

ZIP versus GZIP

 

 

Os usuários do Windows estão familiarizados com o formato ZIP, que serve tanto para armazenar arquivos, como para comprimir dados.

 

Os usuários Linux/Unix usam também o formato GZIP, que apenas comprime dados, não servido como arquivador, pois este formato não permite mais de um arquivo armazenado nele. Geralmente usuários 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 extensão no padrão tar.gz.

 

java.io.File e Streams

 

 

 

A classe java.io.File é uma representação abstrata para o caminho de um arquivo ou diretório, independente do sistema operacional ou sistema de arquivos utilizado.

 

Esta classe possui muitos métodos úteis para a obtenção de informações do arquivo em disco, verificar se ele existe, se é arquivo ou diretório, criar a estrutura de diretórios representada, criar um arquivo vazio em disco, listar os diretórios e arquivos etc.

 

Para mais detalhes da classe, consulte a documentação em: File (Java 2 Platform SE v1.4.2).

 

Os streams (ou fluxos) são a base da comunicação dos dados. Eles são como os dutos que transportam os bytes de um lado para o outro.

 

Stream é um conceito genérico para o transporte de dados, que é utilizado em diversas necessidades como acesso a arquivo em disco, comunicação de rede via sockets etc.

 

Basicamente existem dois tipos de streams: os de entrada, chamados de input stream; e os de saída, chamados output stream.

 

Os streams de entrada servem para ler ou receber dados oriundos de alguma fonte, já os de saída, consequentemente, servem para enviar ou gravar dados para outro destino.

 

Em comum, as classes que implementam os streams de entrada fornecem o método read() para leitura dos dados e as classes que implementam os stream de saída fornecem o método write() para gravar os dados.

 

Para estar dentro do padrão 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 saída, que devem implementar a interface java.io.OutputStream.

 

As classes BufferedInputStream e BufferedOutputStream implementam o conceito de buffer para a leitura e gravação de bytes via streams.

 

O uso dessas classes torna os acesso mais performáticos, pois trabalham com os bytes de dados em memória e evita o acesso direto a todo instante ao disco, por exemplo.

 

Mais informações, consulte o tutorial oficial da Sun: Trail: Essential Classes: Table of Contents (The Java™ Tutorials)

Link para o comentário
Compartilhar em outros sites

Este tópico está impedido de receber novos posts.
 Compartilhar

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...

Informação Importante

Nós fazemos uso de cookies no seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies , caso contrário, vamos supor que você está bem para continuar.