Professional Documents
Culture Documents
Muestra brevemente las generalidades de la compresin de datos Describe el paquete java.util.zip Muestra cmo usar este paquete para comprimir y descomprimir datos Muestras cmo comprimir y descomprimir objetos serializados para ahorrar espacio en disco Muestras cmo comprimir y descomprimir datos al vuelo (antes de transmitir) para mejorar el desempeo de aplicaciones cliente/servidor
Este cadena puede ser codificada y compactada reemplazando cada subcadena repetida de carcteres por un solo carcter repetido y un nmero que representa el nmero de veces que el carcter se repite. La cadena anterior puede codificarse as:
4B2H2D4X4K2W4Z
http://www.javahispano.com
Aqui, "4B" significa cuatro B's, "2H" significa dos H's, y asi sucesivamente. La compresin de cadenas con este algoritmo se conoce somo RLE (run-length encoding) Como otro ejemplo, considera el almacenamiento de una imagen rectangular. Como una sencilla imagen de un mapa de bits, sta se puede almacenar como muestra la Figura 1. 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000111111111111111111110000000000 0000000000100000000000000000010000000000 0000000000100000000000000000010000000000 0000000000100000000000000000010000000000 0000000000111111111111111111110000000000 0000000000000000000000000000000000000000
Fig.1. Un mapa de bits con informacin para el RLE
Que significa; el rectngulo comienza en la coordenada (11,3) y tiene 20 pixeles de ancho y 5 de alto. La imagen rectangular puede comprimirse utilizando RLE a travs del conteo de bits identicos, as:
0, 40 0, 40 0,10 1,20 0,10 0,10 1,1 0,18 1,1 0,10 0,10 1,1 0,18 1,1 0,10 0,10 1,1 0,18 1,1 0,10 0,10 1,20 0,10 0,40
La primera lnea nos dice que la primera lnea del mapa de bit's consiste en 40 0's. La tercera lnea dice que la tercera lnea del mapa de bit's consiste en 10 0's seguidos por 20 1's y seguidos por 10 0's ms, y as sucesivamente para las otras lneas. Nota que RLE requiere representaciones diferentes para el archivo y su versin codificada, dependiendo del tipo del archivo. Por consiguiente, este mtodo no puede trabajar con todos los tipos de archivos. Otras tcnicas de compresion incluyen variaciones de RLE, una es VLE variable-length encoding (tambin conocida como cdigo Huffman ), y muchas otros. Para ms informacin, hay muchos libros disponible sobre tcnicas de compresion de datos imagenes. Hay muchos beneficios con la compresin de los datos. No obtante, la ventaja principal es reducir requisitos de almacenamiento. Tambin, para comunicaciones de los datos, la transferencia de datos comprimidos aumenta la cantidad de informacin transmitida. Observa que la compresin de datos puede implementarse con el hardware ya existente, con software o usando dispositivos de hardware especiales que ya incorporn las tcnicas de compresin. La Figura 2 muestra un diagrama de bloques basico sobre la compresin de datos.
2
_________________ ----------------------> Compresion ----------------------> Datos Originales de Datos comprimidos <--------------------datos <--------------------_________________
ZIP vs GZIP
Si ests trabajando con Windows, podras estar familiarizado con la herramienta WinZip que se utiliza para crear un archivo comprimido y para extraer archivos de un archivo comprimido. En UNIX, sin embargo, las cosas se hace de forma ligeramente distinta. El comando tar se usa para crear un archivo (no comprimido) y otro programa (gzip o compress) se usa para comprimir el archivo.
El paquete java.util.zip
Java proporciona el paquete java.util.zip para trabajar con archivos compatibles con el formato zip. este paquete proporciona clases que te permiten leer, crear, y modificar archivos con los formatos ZIP y GZIP. Tambin te proporciona clases para comprobar las sumas de control de flujos de entrada y puede ser usado para validar entradas de datos. Este paquete proporciona una interface, catorce clases, y dos clases de excepciones como se muestra en la Tabla 1.
Tabla 1. El paquete java.util.zip
Elemento
Checksum
Tipo Interface Clase Clase Clase Clase Clase Clase Clase Clase
Descripcin Representa un dato de la suma de control (data checksum). Implementado por las clases Adler32 y
CRC32
Adler32
CheckedInputStream
Usado para calcular la suma de control(checksum) de un flujo de datos Un flujo de entrada que guarda la suma de control de los datos que estan siendo ledos. Un flujo de salida que guarda la suma de control de los datos que estan siendo escritos. Usado para calcular la suma de control CRC32 de un flujo de datos Soporte para compresion usando la librera ZLIB Un flujo de salida filtrado para la compresin de datos con el formato de compresin deflate Un flujo de entrada filtrado para leer datos comprimidos con el formtato GZIP Un flujo de salida filtrado para escribir datos comprimidos con el formato GZIP
3
GZIPInputStream
GZIPOutputStream
http://www.javahispano.com
Clase Clase Clase Clase Clase Clase Clase de excepcion Clase de exepcion
Soporte para descompresion usando la librera ZLIB Un flujo de entrada filtrado para la descompresion de datos con el formato de compresin deflate Representa la entrada de un archivo ZIP Usado para leer entradas de un archivo ZIP Un filtro de entrada para leer archivos con el formato
ZIP
Un filtro de salida para escribir archivos con el formato ZIP Lanza una excepcin para sealar un error en el formato de los datos Lanza una excepcin para sealar un error en el formato Zip
DataFormatException ZipException
Nota: La librera de compresin ZLIB se desarroll inicialmente como parte de la norma PNG(Portable Network Graphics) que no esta protegida por patentes.
Una vez creado el flujo de entrada ZIP, puedes leer las entradas del archivo ZIP usando el mtodo getNextEntry que retorna un objeto ZipEntry. Si se alcanza el fin del archivo(EOF) getNextEntry retorna null:
ZipEntry entry; while((entry = zin.getNextEntry()) != null) { // extraer los datos // abrir flujos de salida }
Ahora, es momento de preparar un flujo de salida descomprimido, esto se puede hacer de la siguiente manera:
int BUFFER = 2048; 4
Nota: En el cdigo anterior se uso BufferedOutputStream en lugar de ZIPOutputStream. ZipOutputSream y GZIPOutputStream usan un buffer interno de 512 bytes. El uso de BufferedOutputStream es justificado nicamente cuando el tamao del buffer a utilizar es mucho mas grande que 512 bytes (en este ejemplo 2048). Mientras que ZipOutputSream no te permite modificar el tamao del buffer, GZIPOutputStream si lo hace; puedes especificar el tamao del buffer interno como un argumento de su constructor.
En este segmento de cdigo, un flujo de salida de archivo se crea usando el nombre de la(s) entrada(s) ZIP que puede recuperarse usando el mtodo entry.getName. La fuente de datos ZIP es entonces leida y se escribe en un flujo descomprimido:
while ((count = zin.read(data, 0, BUFFER)) != -1) { //System.out.write(x); dest.write(data, 0, count); }
El codigo del Ejemplo 1 muestra como descomprimir y extraer archivos desde un archivo ZIP. Para probar este ejemplo, compila y ejecuta el ejemplo pasndole como parmetro un archivo comprimido con el formato ZIP:
prompt> java UnZip somefile.zip
Nota que somefile.zip debe ser un archivo creado usando alguna herramienta compatible con el formato ZIP, como WINZIP. Ejemplo 1: UnZip.java
import java.io.*; import java.util.zip.*; public class UnZip { static final int BUFFER = 2048; public static void main (String argv[]) { try { BufferedOutputStream dest = null; FileInputStream fis = new FileInputStream(argv[0]); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis)); ZipEntry entry; 5
http://www.javahispano.com
while((entry = zis.getNextEntry()) != null) { System.out.println("Extracting: " +entry); int count; byte data[] = new byte[BUFFER]; // Escribir los archivos en el disco FileOutputStream fos = new FileOutputStream(entry.getName()); dest = new BufferedOutputStream(fos, BUFFER); while ((count = zis.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, count); } dest.flush(); dest.close(); } zis.close(); } catch(Exception e) { e.printStackTrace(); } } }
Es importante hacer notar que la clase ZipInputStream lee archivos ZIP secuencialmente. La clase ZipFile, en cambio, lee el contenido de un archivo ZIP usando un acceso aleatorio interno para que las entradas del archivo ZIP no tengan que ser ledas secuencialmente
Nota: Otra diferencia fundamental entre ZipInputSream y ZipFile s en terminos del uso de la memoria de intercambio(chach). Las entradas ZIP no son puestas en la memoria de intercambio cuando el archivo es leido usando una combinacin de ZipInputStream y FileInputStream. Por otro lado, si el archivo es abierto usando ZipFile(fileName) entonces es colocado en la memoria intermedia, as si ZipFile(fileName) es llamado otra vez el archivo slo se abre una vez. Si trabajas sobre UNIX, cabe mencionar que todos los archivos ZIP abiertos usando ZipFile son abiertos utilizando memoria mapeada, y por consiguiente el desempeo de ZipFile es superior a ZipInputStream. Por otro lado, si los contenidos del mismo ZIP, son frecuentemente modificados y recargados durante la ejecucin del programa entonces se recomienda el uso de ZipInputStream
A continuacion se muestra como se puede descomprimir un archivo ZIP utilizando la clase ZipFile: 1. Crear un objeto ZipFile especificando el archivo ZIP a ser leido, ya sea como un String o como un objeto File:
ZipFile zipfile = new ZipFile("figs.zip");
2. Usa el mtodo entries, que retorna un objeto Enumeration, despus con un ciclo obten todos los objetos ZipEntry del archivo a descomprimir:
3. while(e.hasMoreElements()) { 4. entry = (ZipEntry) e.nextElement(); 5. // lee el contenido y guardalo 6. }
7. Lee el contenido de un objeto ZipEntry en especifico dentro del archivo ZIP pasando el objeto ZipEntry al mtodo getInputStream, que retornar un objeto InputStream desde el cual puedes leer el contenido de las entradas:
8. is = new 9. BufferedInputStream(zipfile.getInputStream(entry));
El cdigo completo de este programa se muestra en el ejemplo 2. Nuevamente, para probar esta clase, compilalo y ejecutalo pasandole como argumento un archivo con formato ZIP:
prompt> java UnZip2 somefile.zip
Ejemplo 2: UnZip2.java
import java.io.*; import java.util.*; import java.util.zip.*; public class UnZip2 { static final int BUFFER = 2048; public static void main (String argv[]) { try { BufferedOutputStream dest = null; BufferedInputStream is = null; ZipEntry entry; ZipFile zipfile = new ZipFile(argv[0]); Enumeration e = zipfile.entries(); while(e.hasMoreElements()) { entry = (ZipEntry) e.nextElement(); System.out.println("Extracting: " +entry); is = new BufferedInputStream (zipfile.getInputStream(entry)); int count; byte data[] = new byte[BUFFER]; FileOutputStream fos = new FileOutputStream(entry.getName()); 7
http://www.javahispano.com
dest = new BufferedOutputStream(fos, BUFFER); while ((count = is.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, count); } dest.flush(); dest.close(); is.close(); } } catch(Exception e) { e.printStackTrace(); } } }
6. Una vez que el flujo de salida objetivo es creado, el siguiente paso es abrir el o los archivos origen de los datos. En este ejemplo, los archivos a partir de los cuales se crear el archivo ZIP son aquellos archivos que se escuentren en el directorio actual. El mtodo list se usa en este ejemplo para obtener la lista de los archivos que se encuentran en el directorio actual:
7. 8. 9. 10. 11. 12. 13. 14. File f = new File("."); String files[] = f.list(); for (int i=0; i<files.length; i++) { System.out.println("Adding: "+files[i]); FileInputStream fi = new FileInputStream(files[i]); // creamos una entrada zip // agregamos entradas zip al archivo }
Nota:El cdigo anterior es capaz de comprimir todos los archivos que se encuentran en el directorio actual. No maneja subdirectorios. Como ejercicio, puedes intentar modificar el ejemplo 3 para que maneje subdirectorios
17. Antes de escribir los datos en el flujo ZIP de salida, debes primero colocarle el objeto ZipEntry usando el mtodo putNextEntry:
18. 20. 21. 22. 23. out.putNextEntry(entry); int count; while((count = origin.read(data, 0, BUFFER)) != -1) { out.write(data, 0, count); }
http://www.javahispano.com
Nota:Las entradas pueden ser agregadas al archivo ZIP de forma comprimida (DEFLATED) o no comprimida(STORED). El mtodo setMethod se usa para fijar el mtodo de almacenamiento. Por ejemplo, para fijar el mtodo a DEFLATED (comprimido) es: out.setMethod(ZipOutputStream.DEFLATED) y para fijarlo a STORED (no comprimido) es: out.setMethod(ZipOutputStream.DEFLATED)
Mtodo
public String getComment() public long getCompressedSize() public int getMethod() public String getName() public long getSize()
public long getTime() public void setComment(String c) public void setMethod(int method) public void setSize(long size) public void setTime(long time)
Retorna el tamao comprimido de la entrada, -1 si se desconoce Retorna el mtodo de compresin de la entrada, -1 si no esta especificado Retorna el nombre de la entrada Retorna el tamao sin comprimir de la entrada, -1 si se desconoce Retorna la fecha de modificacion de la entrada, -1 si no esta especificado Fija un comentario opcional para la entrada Fija el mtodo de compresion para la entrada Fija el tamao sin comprimir de la entrada Fija la fecha de modificacion de la entrada
10
las sumas de comprobacin se usan para detectar archivos o mensajes corruptos. Por ejemplo, imagina que quieres crear un archivo ZIP y despus transferirlo a una PC remota. Una vez que est en la mquina remota, usando la suma de comprobacion, puedes verificar si el archivo se corrompi durante la transmisin. Para mostrar como crear una suma de verificacion, hemos modificado el ejemplo 1 y el ejemplo 3, para usar CheckedInputStream y CheckedOutputStream, sto se muestra en los ejemplos 4 y 5. Ejemplo 4: Zip.java
import java.io.*; import java.util.zip.*; public class Zip { static final int BUFFER = 2048; public static void main (String argv[]) { try { BufferedInputStream origin = null; FileOutputStream dest = new FileOutputStream("c:\\temp\\myfigs.zip"); CheckedOutputStream checksum = new CheckedOutputStream(dest, new Adler32()); ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(checksum)); //out.setMethod(ZipOutputStream.DEFLATED); byte data[] = new byte[BUFFER]; // get a list of files from current directory File f = new File("."); String files[] = f.list(); for (int i=0; i<files.length; i++) { System.out.println("Adding: "+files[i]); FileInputStream fi = new FileInputStream(files[i]); origin = new BufferedInputStream(fi, BUFFER); ZipEntry entry = new ZipEntry(files[i]); out.putNextEntry(entry); int count; while((count = origin.read(data, 0, BUFFER)) != -1) { out.write(data, 0, count); } origin.close(); } out.close(); System.out.println("checksum: "+checksum.getChecksum().getValue()); } catch(Exception e) { e.printStackTrace(); } } }
Ejemplo 5: UnZip.java
import java.io.*; import java.util.zip.*; public class UnZip { 11
http://www.javahispano.com
public static void main (String argv[]) { try { final int BUFFER = 2048; BufferedOutputStream dest = null; FileInputStream fis = new FileInputStream("c:\\temp\\myfigs.zip"); CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32()); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum)); ZipEntry entry; while((entry = zis.getNextEntry()) != null) { System.out.println("Extracting: " +entry); int count; byte data[] = new byte[BUFFER]; // write the files to the disk FileOutputStream fos = new FileOutputStream(entry.getName()); dest = new BufferedOutputStream(fos, BUFFER); while ((count = zis.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, count); } dest.flush(); dest.close(); } zis.close(); System.out.println("Checksum: "+checksum.getChecksum().getValue()); } catch(Exception e) { e.printStackTrace(); } } }
Para probar los ejemplos 4 y 5, compila las clases y ejecuta primero la clase Zip para crear un archivo ZIP (se calcular el valor de la suma de comprobacin y se mostrar en la pantalla ), despus ejecuta la clase UnZip para descomprimir el archivo (nuevamente el valor de la suma de comprobacin se mostrar en la pantalla). Los dos valores deben ser exactamente iguales, de no ser as, el archivo esta corrupto. Las sumas de verificacion son muy tiles para validar datos. Por ejemplo, puedes crear un archivo ZIP y enviarselo a un amigo junto con el valor de la suma de comprobacin. Tu amigo descomprime el archivo y compara el valor de la suma de comprobacin, si los dos valores son iguales, tu amigo sabe que el archivo es autntico.
Comprimiendo de Objetos
Hemos visto cmo comprimir datos que se encuentran en forma de archivos y agregarlos a un archivo apropiado. Pero qu sucede si los datos que deseamos comprimir no estn disponible en un archivo? Asumamos por ejemplo, que estas transfiriendo objetos grandes a travs de sockets. Para mejorar el desempeo de tu aplicacin, puedes querer comprimir los objetos antes de enviarlos por la red y descomprimirlos cuando lleguen a su destino. Como otro ejemplo, digamos quieres guardar objetos en el disco en un formato comprimido. El formato ZIP que esta basado en registros, no es muy conveniente para este trabajo. El formato GZIP es ms apropiado ya que funciona como un sencillo flujo de datos.
12
Ahora, veamos un ejemplo de cmo comprimir objetos antes de escribirlos en el disco y cmo descomprimirlos despus de leerlos del disco. El ejemplo 6 es una sencilla clase que implementa la interface Serializable para indicarle a la JVM que deseamos serializar instancias de esta clase. Ejemplo 6: Employee.java
import java.io.*; public class Employee implements Serializable { String name; int age; int salary; public Employee(String name, int age, int salary) { this.name = name; this.age = age; this.salary = salary; } public void print() { System.out.println("Record for: "+name); System.out.println("Name: "+name); System.out.println("Age: "+age); System.out.println("Salary: "+salary); } }
Ahora, escribamos otra clase que cree un par de objetos de la clase Employee. El ejemplo 7 crea dos objetos (sarah y sam) de la clase Employee, despues guarda su estado en un archivo con un formato comprimido. Ejemplo 7 SaveEmployee.java
import java.io.*; import java.util.zip.*; public class SaveEmployee { public static void main(String argv[]) throws Exception { // creamos algunos objetos Employee sarah = new Employee("S. Jordan", 28, 56000); Employee sam = new Employee("S. McDonald", 29, 58000); // Serializamos los objetos sarah y sam FileOutputStream fos = new FileOutputStream("db"); GZIPOutputStream gz = new GZIPOutputStream(fos); ObjectOutputStream oos = new ObjectOutputStream(gz); oos.writeObject(sarah); oos.writeObject(sam); oos.flush(); oos.close(); fos.close(); } } Ahora, la clase ReadEmployee mostrada en el ejemplo 8, se usa para reconstruir el estado de los dos objetos. Una vez que el estado se ha reconstruido, el mtodo print se invoca en ellos. 13
http://www.javahispano.com
Ejemplo 8: ReadEmployee.java
import java.io.*; import java.util.zip.*; public class ReadEmployee { public static void main(String argv[]) throws Exception{ //des-serializamos los objetos sarah y sam FileInputStream fis = new FileInputStream("db"); GZIPInputStream gs = new GZIPInputStream(fis); ObjectInputStream ois = new ObjectInputStream(gs); Employee sarah = (Employee) ois.readObject(); Employee sam = (Employee) ois.readObject(); //mostramos los datos despues de reconstruir el estado de los objetos sarah.print(); sam.print(); ois.close(); fis.close(); } }
La misma idea se puede usar para comprimir grandes objetos que son enviados a travs de sockets. El siguiente fragmento de cdigo muestra como escribir objetos en un formato comprimido, desde el lado del servidor para el cliente:
// escribir para el cliente GZIPOutputStream gzipout = new GZIPOutputStream(socket.getOutputStream()); ObjectOutputStream oos = new ObjectOutputStream(gzipout); oos.writeObject(obj); gzipos.finish();
Y, el siguiente fragmento de cdigo muestra como descomprimir estos objetos del lado del cliente una vez recibidos desde el servidor:
// leer desde el servidor Socket socket = new Socket(remoteServerIP, PORT); GZIPInputStream gzipin = new GZIPInputStream(socket.getInputStream()); ObjectInputStream ois = new ObjectInputStream(gzipin); Object o = ois.readObject();
Conclusin
Este artculo plante las APIs que puedes usar para comprimir y descomprimir datos desde tus aplicaciones, con ejemplos a lo largo del artculo se mostr cmo usar el paquete
14
para comprimir y descomprimir datos. Ahora tienes las herramientas necesarias para comprimir y descomprimir datos desde tus aplicaciones Java.
java.util.zip
El artculo tambin muestra cmo comprimir y descomprimir datos al vuelo para reducir el trfico de la red y mejorar el desempeo de tus aplicaciones cliente/servidor. La compresion de datos al vuelo , sin embargo, slo mejora el desempeo de aplicaciones cliente/servidor cuando los objetos que estn siendo comprimidos tiene un par de cientos de bytes como tamao. No podras observar sta mejora si los objetos que se estn comprimiendo y transfiriendo son sencillos objetos String, por ejemplo. Para ms informacin
El paquete java.util.zip El paquete java.util.jar Serializacin de Objetos Transportanto objetos sobre Sockets
Isaac Ruz, RuGI , egresado del ITI (Istmo de Tehuantepec, Oaxaca, Mexico) en la Carrera de Ingeniera en Sistemas Computacionales, es actualmente desarrollador independiente Java con intenciones de realizar el examen de certificacin Cuando no esta programando o navegando (?) le gusta mucho leer todo aquello que le de el mas pequeo indicio de como llegar al Valhala o por lo menos a Avalon =:D Para cualquier duda o comentario: RuGI@javahispano.com
15