Un poco de contexto: comúnmente al procesar información que implique generación de archivos éstos tienen que ser almacenados en algún servidor. Lo más probable, para cualquier aplicación de este tipo es que:

  1. Los archivos se almacenen a manera de respaldo en algún directorio.
  2. Sea necesario comprimir estos archivos para conservar espacio en disco o consolidar la información de los mismos.
  3. El servidor donde se almacenan estos archivos es un Unix/Linux.

Por lo tanto, no es del todo usable generar archivos .zip mediante el API de Java (paquete java.util.zip) pues al estar almacenados en un servidor Unix/Linux se generan problemas de logística, conversión y a veces incluso de corrupción de datos.

Así entonces, es necesario generar archivos tar.gz para compresión y archivado, pero la especificación GZIP, implementada en la clase java.util.zip.GZipOutputStream, sólo comprime (es decir, sólo genera un archivo de salida por cada archivo de entrada) mientras que la especificación TAR sólo archiva (o dicho de otra manera, sólo puede generar un archivo de salida a partir de varios de entrada, pero no comprime).

Java Tar

Por ahí de 1990, Tim Endres fundó la empresa ICE Engineering, y entre otras librerías que creó en parte por hobby y en parte para facilitar su trabajo, incluye el paquete com.ice.tar que proporciona la implementación de tar.gz que necesitamos:

com.ice.tar package

Paquete com.ice.tar visto desde el Eclipse.

En general la librería es buena, aunque tiene sus detalles:

• Es necesario asignarle un tamaño al TarEntry para que el búfer interno pueda ser configurado.

•Es necesario cerrar explícitamente el TarEntry para que el búfer genere un flush() y escriba en el OutputStream. De lo contrario, al abrir el archivo (mediante #gunzip -f archivo.tar.gz) nos marcará un error de «Final inesperado de archivo; pasando a siguiente cabecera».

• En estricta teoría se supone que el TarGzOutputStream ya hace uso de un BufferedOutputStream de manera interna (de ahí los dos puntos anteriores), pero tengo mis sospechas de que esto no es cierto por lo que si se requiere desempeño, tal vez sea necesario agregar los filtros del paquete java.nio. Por el momento dejémoslo así.

Tomando en cuenta estos detalles, podemos hacer un código que lea varios archivos y los archive y comprima en un tar.gz:

  public void tarGz(final List filePaths, final String zipPath) {

    FileInputStream inputStream = null;
    BufferedInputStream bufferedInputStream = null;

    FileOutputStream outputStream = null;
    TarGzOutputStream tarOutputStream = null;

    try {
      outputStream = new FileOutputStream(zipPath.toString());
      tarOutputStream = new TarGzOutputStream(outputStream);

      byte data[] = new byte[1024];
      int dataSize = 0;

      for (Iterator t = filePaths.iterator(); t.hasNext();) {
        String cdrPath = «» + t.next();
        String cdrName = CDRUtils.getFileNameFromPath(cdrPath);

        TarEntry entry = new TarEntry(cdrName);
        entry.setSize(new File(cdrPath).length()); //Esto es a wilson
        entry.setName(cdrName);
        tarOutputStream.putNextEntry(entry);

        inputStream = new FileInputStream(cdrPath);
        bufferedInputStream = new BufferedInputStream(inputStream, data.length);

        while ((dataSize = bufferedInputStream.read(data, 0, data.length)) != -1) {
          tarOutputStream.write(data, 0, dataSize);
        }

        tarOutputStream.closeEntry(); //Esto es a wilson

        if (bufferedInputStream != null) {
          bufferedInputStream.close();
        }

        if (inputStream != null) {
          inputStream.close();
        }
      }
    } catch (final Exception e) {
      logger.error(«Error IO», e);
    } finally {
      try {
        if (bufferedInputStream != null) {
          bufferedInputStream.close();
        }

        if (inputStream != null) {
          inputStream.close();
        }

        if (tarOutputStream != null) {
          tarOutputStream.close();
        }

        if (outputStream != null) {
          outputStream.close();
        }
      } catch (final IOException ioe) {
        logger.error(«Resource release error», ioe);
      }
    }
  }

Actualización: 23/06/2011

Si lo que buscamos es la operación inversa (extraer los archivos de un tar.gz) es bastante más sencillo programarlo en Java, sin embargo es necesario considerar un pequeño detalle: un archivo tar.gz consiste de varios documentos o carpetas archivados dentro de un .tar que posteriormente es sometido a una operación de compresión mediante gzip. Es decir, primero hay que descomprimir y posteriormente «desarchivar», como se hace normalmente con los comandos de Unix/Linux: gunzip archivo.tar.gz \n tar -xvf archivo.tar

public static void unTarGz(final String zipPath, final String unZipPath) throws Exception {

  //java.util.zip.GZIPInputStream forma parte del API estandar de Java
  GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(zipPath));

  //Path y nombre del archivo temporal
  String tempDir = unZipPath.substring(0, unZipPath.lastIndexOf(‘/’));
  String tempFile = «» + new Date().getTime() + «.tmp»;
  String tempPath = tempDir + «/» + tempFile;

  //Primero se crea un temporal que contiene el .tar «gunzipeado»…
  OutputStream out = new FileOutputStream(tempPath);

  byte[] data = new byte[1024];
  int len;
  while ((len = gzipInputStream.read(data)) > 0) {
    out.write(data, 0, len);
  }

  gzipInputStream.close();
  out.close();

  //…Y luego mediante el paquete com.ice.tar extraemos la informacion del .tar temporal
  TarArchive tarArchive = new TarArchive(new FileInputStream(tempPath));
  tarArchive.extractContents(new File(unZipPath));
  tarArchive.closeArchive();

  //Borrado del archivo remporal (.tar)
  new File(tempPath).delete();
}