CodeGym /Cursos /JAVA 25 SELF /Archivos/compresión: java.util.zip

Archivos/compresión: java.util.zip

JAVA 25 SELF
Nivel 41 , Lección 4
Disponible

1. Introducción: para qué sirven los archivos y la compresión en Java

En el mundo actual, trabajar con archivos y ficheros comprimidos es una tarea habitual: copias de seguridad, intercambio de archivos, registro, almacenamiento de grandes volúmenes de datos. Java proporciona medios estándar para trabajar con archivos ZIP y ficheros GZIP a través del paquete java.util.zip.

Qué puede hacer Java:

  • Leer y crear archivos ZIP (contenedores de múltiples archivos).
  • Leer y crear archivos GZIP (compresión de un único archivo).
  • Gestionar el contenido de los archivos, filtrar archivos por máscara.
  • Controlar el nivel de compresión.
  • Comprobar la seguridad al descomprimir (defensa frente a zip slip y zip bomb).

2. Clases principales

ZipInputStream y ZipOutputStream

Son clases de flujo para la lectura/escritura secuencial de archivos ZIP. Cuándo usar: si necesitas leer o crear un archivo «sobre la marcha», sin acceso aleatorio a archivos individuales.

Ejemplo: lectura de un archivo ZIP

import java.io.*;
import java.util.zip.*;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        System.out.println("Archivo: " + entry.getName());
        // Se puede leer el contenido de la entry mediante zis.read(...)
        zis.closeEntry();
    }
}

Ejemplo: creación de un archivo ZIP

import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    ZipEntry entry = new ZipEntry("hello.txt");
    zos.putNextEntry(entry);
    zos.write("¡Hola, archivo!".getBytes());
    zos.closeEntry();
}

ZipFile

Clase para acceso aleatorio al contenido de un archivo ZIP: se puede obtener rápidamente la lista de archivos, abrir cualquiera por su nombre y leer su contenido.

Ejemplo:

import java.util.zip.*;
import java.io.*;

ZipFile zipFile = new ZipFile("archive.zip");
zipFile.stream().forEach(entry -> System.out.println(entry.getName()));

ZipEntry entry = zipFile.getEntry("hello.txt");
try (InputStream is = zipFile.getInputStream(entry)) {
    // Leemos el contenido del archivo
}
zipFile.close();

¿Cuándo usar ZipFile?

  • Si necesitas obtener rápidamente la lista de archivos, metadatos, tamaño y fecha.
  • Si necesitas leer archivos concretos sin recorrer secuencialmente todo el archivo ZIP.

ZipEntry

Objeto que representa un archivo o carpeta dentro del archivo ZIP. Contiene nombre, tamaño, fecha, flags, nivel de compresión, etc.

import java.util.zip.ZipEntry;

ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("Descripción del archivo");
entry.setTime(System.currentTimeMillis());

Niveles de compresión (Deflater)

Al crear un archivo ZIP puedes controlar el grado de compresión (del 0 — sin compresión, hasta 9 — compresión máxima):

import java.io.FileOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipOutputStream;

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    zos.setLevel(Deflater.BEST_COMPRESSION); // o 0..9
    // ...
}
  • Deflater.NO_COMPRESSION (0)
  • Deflater.BEST_SPEED (1)
  • Deflater.BEST_COMPRESSION (9)
  • Deflater.DEFAULT_COMPRESSION (-1)

Regla: cuanto mayor es el nivel — más lento, pero comprime mejor.

3. Compresión de un solo archivo

GZIP es un formato para comprimir un único archivo (no un contenedor). Se usa para registros, archivos temporales y transferencia por red.

Ejemplo: comprimir un archivo

import java.util.zip.*;
import java.io.*;

try (GZIPOutputStream gos = new GZIPOutputStream(new FileOutputStream("file.txt.gz"));
     FileInputStream fis = new FileInputStream("file.txt")) {
    fis.transferTo(gos);
}

Ejemplo: descomprimir un archivo

import java.util.zip.GZIPInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

try (GZIPInputStream gis = new GZIPInputStream(new FileInputStream("file.txt.gz"));
     FileOutputStream fos = new FileOutputStream("file.txt")) {
    gis.transferTo(fos);
}

Recuerda: GZIP solo funciona con un único archivo — no conserva la estructura de carpetas ni metadatos adicionales. En cambio, ZIP permite empaquetar varios archivos y carpetas completas, conservando su estructura y la información de cada elemento.

4. Compresión/descompresión de directorios, filtros con PathMatcher

Comprimir un directorio en ZIP

Para comprimir una carpeta con archivos y subcarpetas, recorremos recursivamente el árbol de archivos y añadimos cada archivo al archivo ZIP con la ruta relativa correcta (los separadores en ZIP son siempre "/").

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Path sourceDir = Paths.get("myfolder");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    Files.walk(sourceDir)
        .filter(Files::isRegularFile)
        .forEach(path -> {
            String entryName = sourceDir.relativize(path).toString().replace("\\", "/");
            try (InputStream is = Files.newInputStream(path)) {
                zos.putNextEntry(new ZipEntry(entryName));
                is.transferTo(zos);
                zos.closeEntry();
            } catch (IOException e) { e.printStackTrace(); }
        });
}

Descomprimir un ZIP en un directorio

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = Paths.get("output", entry.getName());
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Filtrado de archivos por máscara (PathMatcher)

Puedes filtrar archivos para comprimir/descomprimir por máscara, por ejemplo solo "*.txt":

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.PathMatcher;

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.txt");
Files.walk(sourceDir)
    .filter(matcher::matches)
    .forEach(/* ... */);

5. Seguridad: Zip Slip, zip bomb, verificación de normalización de ruta

Zip Slip (ataque mediante rutas)

Problema: Un atacante puede crear un archivo ZIP con un archivo cuyo nombre es "../../../../etc/passwd". Al descomprimir sin comprobaciones, ese archivo podría sobreescribir archivos del sistema.

Solución: antes de escribir el archivo, normaliza la ruta y asegúrate de que no sale del directorio de destino.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

Path targetDir = Paths.get("output").toAbsolutePath();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = targetDir.resolve(entry.getName()).normalize();
        if (!outPath.startsWith(targetDir)) {
            throw new IOException("Zip Slip: intento de escritura fuera de la carpeta de destino!");
        }
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Zip bomb (bomba ZIP)

Problema: un archivo ZIP puede contener un archivo que, tras descomprimirlo, ocupa gigabytes, aunque el propio ZIP pese unos pocos kilobytes. Esto puede «matar» un servidor o el disco.

Solución: limita el tamaño máximo de los archivos descomprimidos y el volumen total de descompresión, interrumpiendo el proceso al superar el límite.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

long maxSize = 100 * 1024 * 1024; // 100 MB
long totalUnzipped = 0;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = Paths.get("output", entry.getName()).normalize();
        Files.createDirectories(outPath.getParent());

        long written = 0;
        try (OutputStream os = Files.newOutputStream(outPath)) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = zis.read(buf)) > 0) {
                os.write(buf, 0, len);
                written += len;
                totalUnzipped += len;
                if (written > maxSize || totalUnzipped > maxSize) {
                    throw new IOException("Zip bomb detected!");
                }
            }
        }
        zis.closeEntry();
    }
}

6. Práctica: utilidad CLI «zip/unzip» con máscaras

Escribamos una sencilla utilidad de consola para comprimir y descomprimir archivos con soporte de máscaras.

Ejemplo: compresión

// java ZipUtil zip myfolder archive.zip *.txt
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public static void zip(String sourceDir, String zipFile, String glob) throws IOException {
    Path src = Paths.get(sourceDir);
    PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
        Files.walk(src)
            .filter(Files::isRegularFile)
            .filter(matcher::matches)
            .forEach(path -> {
                String entryName = src.relativize(path).toString().replace("\\", "/");
                try (InputStream is = Files.newInputStream(path)) {
                    zos.putNextEntry(new ZipEntry(entryName));
                    is.transferTo(zos);
                    zos.closeEntry();
                } catch (IOException e) { e.printStackTrace(); }
            });
    }
}

Ejemplo: descompresión con protección contra Zip Slip

// java ZipUtil unzip archive.zip output
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public static void unzip(String zipFile, String outDir) throws IOException {
    Path targetDir = Paths.get(outDir).toAbsolutePath();
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
            Path outPath = targetDir.resolve(entry.getName()).normalize();
            if (!outPath.startsWith(targetDir)) {
                throw new IOException("Zip Slip: intento de escritura fuera de la carpeta de destino!");
            }
            if (entry.isDirectory()) {
                Files.createDirectories(outPath);
            } else {
                Files.createDirectories(outPath.getParent());
                try (OutputStream os = Files.newOutputStream(outPath)) {
                    zis.transferTo(os);
                }
            }
            zis.closeEntry();
        }
    }
}

Ejemplo de ejecución:

java ZipUtil zip myfolder archive.zip "*.txt"
java ZipUtil unzip archive.zip output

En el primer ejemplo, el comando java ZipUtil zip myfolder archive.zip "*.txt" comprime todos los archivos .txt de la carpeta myfolder en el archivo archive.zip. En el segundo ejemplo, java ZipUtil unzip archive.zip output descomprime el archivo ZIP en la carpeta output, comprobando que ningún archivo se escriba fuera del directorio de destino — eso es la protección contra Zip Slip.

1
Cuestionario/control
Optimización de IO, nivel 41, lección 4
No disponible
Optimización de IO
Optimización de IO
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION