Componentes clave
Clases Métodos
java.util.zip.DeflaterOutputStream void write(int valbyte) void close( )
java.util.zip.InflaterInputStream int read( ) void close( )
Para leer los datos de un archivo comprimido se requieren estos pasos: 1. Cree un flujo de archivo que use InflaterInputStream.
2. Lea de la instancia de InflaterInputStream. Puede usar uno de los métodos estándar de read( ) para leer los datos. Sin embargo, a menudo InflaterInputStream está envuelto en un DataInputStream, que le permite leer convenientemente tipos de datos primitivos, como int o double. En cualquier caso, los datos se descomprimirán automáticamente.
3. Cierre el flujo de entrada cuando haya terminado de escribir.
Análisis
Defl aterOutputStream e Infl aterInputStream son el núcleo de las capacidades de compresión de archivos de Java. Pueden usarse explícitamente (como se hace en la solución), o implícitamente cuando crea un archivo GZIP o ZIP. Defl aterOutputStream escribe datos en un archivo, comprimiéndolos en el proceso. Infl aterInputStream lee los datos de un archivo, descomprimiéndolos en el proceso.
DeflaterOutputStream e InflaterInputStream definen tres constructores, cada uno. He aquí los usados en esta solución.
DeflaterOutputStream(OutputStream flujoSal) InflaterInputStream(InputStream flujoEnt)
Aquí, fl ujoSal especifi ca el fl ujo de salida y fl ujoEnt especifi ca el de entrada. Estos constructores usan el compresor y el descompresor predeterminados. Como se mencionó, éstos son objetos de tipo Infl ater y Defl ater. Proporcionan los algoritmos que realizan la compresión y descompresión real de los datos. Los otros constructores le permiten especifi car un compresor o descompresor, y un tamaño de búfer. Sin embargo, el compresor, el descompresor y el tamaño de búfer son adecuados para casi todas las tareas.
Una vez que están abiertos los flujos basados en compresión, la compresión y la descompresión ocurren automáticamente cada vez que tiene lugar una operación de escritura o lectura. Por tanto, los datos contenidos en un archivo escrito por DeflaterOutputStream estarán en un formato comprimido. Los datos leídos de un archivo comprimido mediante un InflaterInputStream, estarán en su formato descomprimido (es decir, simple). Esto significa que puede almacenar datos en forma comprimida, pero su programa tendrá acceso transparente a él (como si estuvieran almacenados en un archivo no comprimido). Esta es una de las razones por las que la biblioteca de compresión de Java es tan poderosa.
Para escribir datos, puede usar cualquier de los métodos estándar de write( ) definidos por OutputStream. Para leer datos, puede usar cualquiera de los métodos estándar de read( ) definidos por InputStream. (Estos se describen en Escriba bytes en un archivo y Lea bytes de un archivo.)
Sin embargo, a menudo es mejor envolver DeflaterOutputStream en un DataOutputStream y envolver InflaterInputStream en un DataInputStream. Al hacerlo así se tiene acceso a métodos como writeInt( ), writeDouble( ), readInt( ) y readDouble( ), que le permiten leer y escribir datos primitivos de manera conveniente.
Ejemplo
En el siguiente ejemplo se muestra cómo crear un archivo de datos comprimido y luego leer los datos en el archivo. El archivo de datos es una colección de valores double. Sin embargo, el archivo empieza con un entero que contiene una cuenta de double en el archivo. Tome en cuenta que Defl aterOutputStream está envuelto en un DataOutputStream. Infl aterInputStream está envuelto en un DataInputStream.
Esto permite que valores de tipos primitivos, como int o double, se escriban de manera conveniente en una forma comprimida y que luego se vuelvan a leer.
El programa crea el archivo de datos al escribir una matriz de seis valores double en un archivo llamado datos.cmprs. En primer lugar, escribe una cuenta del número de valores que seguirá al llamar a writeInt( ). La cuenta es seis, en el ejemplo. Luego escribe los valores double al llamar a writeDouble( ). Debido a que se usa DeflaterOutputStream, los datos se comprimen automáticamente antes de almacenarse en el archivo.
El programa lee después los valores. Hace esto al obtener primero la cuenta al llamar a readInt( ). Luego lee ese número de valores al llamar a readDouble( ). En el proceso, promedia los valores. Lo que es importante comprender es que el archivo se comprime y descomprime "al vuelo". En ningún momento existe una versión descomprimida del archivo.
Es interesante observar que el archivo comprimido que crea el programa tiene 36 bytes de largo. Sin compresión, el archivo tendría 52 bytes.
// Crea un archivo comprimido de datos al usar un // DeflaterOutputStream y luego leer los datos con // un InflaterInputStream.
//
// Este programa usa la biblioteca de compresión // predeterminada de Java, que es ZLIB. El archivo // comprimido creado por este programa no está en un // formato específico, como ZIP or GZIP. Simplemente // contiene una versión comprimida de los datos. import java.io.*;
import java.util.zip.*; class DemoCompresion {
public static void main(String args[ ]) {
DataOutputStream fout; DataInputStream fin;
double datos[ ] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 }; // Abre el archivo de salida.
try {
fout = new DataOutputStream(
new DeflaterOutputStream(
new FileOutputStream("datos.cmprs"))); } catch(FileNotFoundException exc) {
System.out.println("Error al abrir el archivo de salida"); return;
}
// Comprime los datos usando ZLIB. try {
// Sólo escribe los datos normalmente. El // DeflaterOutputStream los descomprimirá // automáticamente.
// Primero, escribe el tamaño de los datos. fout.writeInt(datos.length);
// Ahora, escribe los datos. for(double d : datos)
fout.writeDouble(d); } catch(IOException exc) {
System.out.println("Error en el archivo comprimido"); }
try {
fout.close( );
} catch(IOException exc) {
System.out.println("Error al cerrar el archivo de salida"); }
// Ahora, abre datos.cmprs para entrada. No es necesario // crear una copia descomprimida del archivo porque la
// descompresión se maneja al vuelo, con InflaterInputStream. // Así, la descompresión es automática y transparente.
try {
fin = new DataInputStream(
new InflaterInputStream(
new FileInputStream("datos.cmprs"))); } catch(FileNotFoundException exc) {
System.out.println("No se ha encontrado el archivo de entrada"); return;
}
// Descomprime el archivo al vuelo. try {
// Primero, recupera la cantidad de datos // contenidos en el archivo.
int num = fin.readInt( ); double prom = 0.0;
double d;
System.out.print("Datos: ");
// Ahora, lee los datos. La descompresión es automática. for(int i=0; i < num; i++) {
d = fin.readDouble( ); prom += d;
System.out.print(d + " "); }
} catch(IOException exc) {
System.out.println("Error al leer el archivo de entrada"); }
try {
fin.close( );
} catch(IOException exc) {
System.out.println("Error al cerrar el archivo de entrada"); }
} }
Aquí se muestra la salida:
Datos: 1.1 2.2 3.3 4.4 5.5 6.6 El promedio es 3.85
Opciones
Como se mencionó, tanto Defl aterOutputStream como Infl aterInputStream ofrecen constructores que le permiten especifi car el compresor, el descompresor o el tamaño del búfer.
La biblioteca de compresión también soporta sumas de verificación a través de las siguientes clases: Alder32 y CRC32. Puede crear flujos que usan una suma de verificación con estas clases: CheckedInputStream y CheckedOutputStream.
Aunque usar DeflaterOutputStream e InflaterInputStream directamente es aceptable, a menudo querrá usar sus subclases:
GZIPInputStream GZIPOutputStream ZipInputStream ZipOutputStream
Estas clases crean archivos comprimidos en formato GZIP o ZIP. La ventaja es que sus archivos de datos estarán en un formato que las herramientas estándar pueden comprender. Sin embargo, si eso no otorga benefi cios a su aplicación, entonces el uso directo de Defl aterOutputStream e Infl aterInputStream es un poco más efi ciente. (En las siguientes soluciones se muestra cómo crear, comprimir y descomprimir un archivo ZIP).
Hay dos formatos de archivo comprimido muy populares: GZIP y ZIP. Java proporciona soporte a ambos. La creación de un archivo GZIP es fácil: simplemente cree un GZIPOutputStream y luego escriba en él. Leer un archivo GZIP es igualmente fácil: sólo cree un GZIPInputStream y luego léalo. Sin embargo, la situación es un poco más complicada si quiere crear un archivo ZIP. En esta solución se crea el procedimiento básico.
Antes de empezar, es importante tomar en cuenta que los archivos ZIP pueden ser muy complejos. En esta solución se muestra cómo crear un esqueleto básico. Si estará trabajando ampliamente con archivos ZIP, necesitará estudiar de cerca las especificaciones tanto de java.util. zip como del propio archivo ZIP.
En general, un archivo ZIP puede contener uno o más archivos comprimidos. Cada archivo está asociado con una entrada que describe el archivo. Se trata de un objeto de tipo ZipEntry. Este tipo de objetos identifica a cada archivo dentro del archivo ZIP. Por tanto, para comprimir un archivo, primero escribirá su ZipEntry y luego sus datos.
Paso a paso
La creación de un archivo ZIP que contenga uno o más archivos comprimidos incluye estos pasos: 1. Cree el archivo ZIP al abrir un ZipOutputStream. Cualquier dato escrito en este flujo se
comprimirá automáticamente.
2. Abra el archivo que se comprimirá. Puede usar cualquier flujo de archivo apropiado, como FileInputStream envuelto en un BufferedInputStream.
3. Cree una ZipEntry para representar el archivo que se está comprimiendo. A menudo el nombre de este archivo se vuelve el nombre de la entrada. Escriba la entrada en la instancia ZipOutputStream al llamar a putNextEntry( ).
4. Escriba el contenido del archivo de entrada en el ZipOutputStream. Los datos se comprimirán automáticamente.
5. Cierra la ZipEntry del archivo de entrada al llamar a closeEntry( ).
6. Por lo general, querrá reportar el progreso de la compresión, incluido el tamaño de reducción del archivo. Para ayudarle en esto, ZipEntry proporciona getSize( ), que obtiene el tamaño sin comprimir del archivo, y getCompressedSize( ), que obtiene el tamaño comprimido.