Trabajar con archivos zip en Java

T

Introducción

En este artículo, cubro los conceptos básicos de la creación, interacción, inspección y extracción de archivos zip usando Java (OpenJDK 11 para ser específico). El ejemplo de código utilizado en este artículo tiene la forma de un proyecto Gradle y está alojado en este repositorio de GitHub para que lo ejecutes y experimentes. Tenga cuidado al cambiar el código que elimina archivos.

Como ya se mencionó, los ejemplos de código aquí están escritos usando Java 11 y utiliza el var palabra clave que se introdujo en Java 10 y paradigmas de programación funcional en Java 8, por lo que se requiere una versión mínima de Java 10 para ejecutarlos como están.

Contenido

  • Clases clave de Java para trabajar con archivos zip
  • Rutas de archivo comunes para los ejemplos de código
  • Inspección del contenido de un archivo zip
  • Extraer un archivo zip
  • Escribir archivos directamente en un nuevo archivo zip
  • Comprimir un archivo existente en un nuevo archivo zip
  • Comprimir una carpeta en un nuevo archivo zip

Clases clave de Java para trabajar con archivos zip

Creo que es una buena idea comenzar identificando algunas de las clases destacadas que se usan comúnmente cuando se trata de archivos zip en Java. Estas clases viven en el java.util.zip o java.nio.file paquetes.

  • java.util.zip.ZipFile se utiliza para leer e interactuar con elementos (ZipEntry instancias) en un archivo zip
  • java.util.zip.ZipEntry es una abstracción que representa un elemento como un archivo o directorio en un archivo zip (es decir, ZipFile ejemplo)
  • java.util.zip.ZipOutputStream es una implementación del resumen Flujo de salida class y se utiliza para escribir elementos en un archivo Zip
  • java.nio.file.Files es una clase de utilidades muy útil para transmitir y copiar datos de archivos en instancias ZipOutputStream o fuera de instancias ZipFile
  • java.nio.file.Path otra clase de utilidades útil para trabajar eficazmente con rutas de archivo

Rutas de archivo comunes para los ejemplos de código

Para el código de ejemplo, utilizo dos directorios comunes para escribir y leer datos desde / hacia los cuales ambos son relativos a la raíz del proyecto Gradle. Eche un vistazo al Repo vinculado en la introducción, o mejor aún, ejecute las muestras. Solo tenga en cuenta estas dos variables de ruta, ya que se utilizan a menudo como directorio de inicio para entradas y salidas.

public class App {

    static final Path zippedDir = Path.of("ZippedData");
    static final Path inputDataDir = Path.of("InputData");
    
    // ... other stuff   
}

Inspección del contenido de un archivo zip

Puede crear una instancia ZipFile class y pasarle la ruta a un archivo zip existente, que esencialmente lo abre como cualquier otro archivo, luego inspecciona el contenido consultando el ZipEntry enumeración contenida en su interior. Tenga en cuenta que ZipFile implementa el AutoCloseable interfaz, lo que lo convierte en un gran candidato para la construcción de programación de Java try-with-resources que se muestra a continuación y en todos los ejemplos aquí.

static void showZipContents() {
    try (var zf = new ZipFile("ZipToInspect.zip")) {
    
        System.out.println(String.format("Inspecting contents of: %sn", zf.getName()));
        
        Enumeration<? extends ZipEntry> zipEntries = zf.entries();
        zipEntries.asIterator().forEachRemaining(entry -> {
            System.out.println(String.format(
                "Item: %s nType: %s nSize: %dn",
                entry.getName(),
                entry.isDirectory() ? "directory" : "file",
                entry.getSize()
            ));
        });
    } catch (IOException e) {
      e.printStackTrace();
    }
}

Ejecutando el proyecto Gradle usando lo siguiente:

$ ./gradlew run

Esto produce salida para el App.showZipContents método de:

> Task :run
Inspecting contents of: ZipToInspect.zip

Item: ZipToInspect/ 
Type: directory 
Size: 0

Item: ZipToInspect/greetings.txt 
Type: file 
Size: 160

Item: ZipToInspect/InnerFolder/ 
Type: directory 
Size: 0

Item: ZipToInspect/InnerFolder/About.txt 
Type: file 
Size: 39

Aquí puede ver que esto imprime todos los archivos y directorios en el archivo zip, incluso los archivos dentro de los directorios.

Extraer un archivo zip

Extraer el contenido de un archivo zip en un disco no requiere nada más que replicar la misma estructura de directorio que está dentro del ZipFile, que se puede determinar mediante ZipEntry.isDirectory y luego copiando los archivos representados en el ZipEntry instancias en disco.

static void unzipAZip() {
    var outputPath = Path.of("UnzippedContents");

    try (var zf = new ZipFile("ZipToInspect.zip")) {
    
        // Delete if exists, then create a fresh empty directory to put the zip archive contents
        initialize(outputPath);

        Enumeration<? extends ZipEntry> zipEntries = zf.entries();
        zipEntries.asIterator().forEachRemaining(entry -> {
            try {
                if (entry.isDirectory()) {
                    var dirToCreate = outputPath.resolve(entry.getName());
                    Files.createDirectories(dirToCreate);
                } else {
                    var fileToCreate = outputPath.resolve(entry.getName());
                    Files.copy(zf.getInputStream(entry), fileToCreate);
                }
            } catch(IOException ei) {
                ei.printStackTrace();
            }
         });
    } catch(IOException e) {
        e.printStackTrace();
    }
}

Escribir archivos directamente en un nuevo archivo zip

Dado que escribir un archivo zip no es más que escribir un flujo de datos en algún destino (un archivo Zip en este caso), escribir datos, como datos de cadena, en un archivo zip solo es diferente en el sentido de que debe coincidir con los datos escrito a ZipEntry instancias agregadas al ZipOutputStream.

De nuevo, ZipOutputStream implementa el AutoCloseable interfaz, por lo que es mejor utilizarla con una instrucción try-with-resources. El único problema real es recordar cerrar su ZipEntry cuando haya terminado con cada uno para dejar en claro cuándo ya no debería recibir datos.

static void zipSomeStrings() {
    Map<String, String> stringsToZip = Map.ofEntries(
        entry("file1", "This is the first file"),
        entry("file2", "This is the second file"),
        entry("file3", "This is the third file")
    );
    var zipPath = zippedDir.resolve("ZipOfStringData.zip");
    try (var zos = new ZipOutputStream(
                            new BufferedOutputStream(Files.newOutputStream(zipPath)))) {
        for (var entry : stringsToZip.entrySet()) {
            zos.putNextEntry(new ZipEntry(entry.getKey()));
            zos.write(entry.getValue().getBytes());
            zos.closeEntry();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Comprimir un archivo existente en un nuevo archivo zip

Si ha copiado un archivo en Java antes, entonces esencialmente ya es un PRO en la creación de un archivo zip a partir de un archivo existente (o directorio para el caso). Nuevamente, la única diferencia real es que debe tener un poco más de precaución para asegurarse de que está haciendo coincidir los archivos con los ZipEntry instancias.

En este ejemplo, creo un archivo de entrada “FileToZip.txt” y escribo algunos datos en él “¡Hola amigos de Java!” y luego usa el Files.copy (ruta, OutputStream) para asociar el ZipEntry con el archivo FileToZip.txt dentro del archivo zip ZippedFile.zip que estoy creando con un ZipOutoutStream ejemplo.

static void zipAFile() {
    var inputPath = inputDataDir.resolve("FileToZip.txt");
    var zipPath = zippedDir.resolve("ZippedFile.zip");
    
    try (var zos = new ZipOutputStream(
                            new BufferedOutputStream(Files.newOutputStream(zipPath)))) {
                            
        Files.writeString(inputPath, "Howdy There Java Friends!n");

        zos.putNextEntry(new ZipEntry(inputPath.toString()));
        Files.copy(inputPath, zos);
        zos.closeEntry();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Comprimir una carpeta en un nuevo archivo zip

Comprimir un directorio no vacío se vuelve un poco más complicado, especialmente si desea mantener directorios vacíos dentro del directorio principal. Para mantener la presencia de un directorio vacío dentro de un archivo zip, debe asegurarse de crear una entrada con el sufijo del separador de directorio del sistema de archivos al crear su ZipEntryy luego ciérrelo inmediatamente.

En este ejemplo, creo un directorio llamado “foldertozip” que contiene la estructura que se muestra a continuación, luego lo comprimo en un archivo zip.

tree .
.
└── foldertozip
    ├── emptydir
    ├── file1.txt
    └── file2.txt

En el siguiente código, observe que utilizo el Files.walk(Path) método para recorrer el árbol de directorios de “foldertozip” y buscar directorios vacíos (“emptydir” en este ejemplo) y si / cuando lo encuentro, concateno el separador de directorios con el nombre dentro del ZipEntry. Después de esto, lo cierro tan pronto como lo agrego al ZipOutputStream ejemplo.

También utilizo un enfoque ligeramente diferente para inyectar los archivos que no son de directorio en el ZipOutputStream en comparación con el último ejemplo, pero solo estoy usando este enfoque diferente por el bien de la variedad en los ejemplos.

static void zipADirectoryWithFiles() {
    var foldertozip = inputDataDir.resolve("foldertozip"); 
    var dirFile1 = foldertozip.resolve("file1.txt");
    var dirFile2 = foldertozip.resolve("file2.txt"); 

    var zipPath = zippedDir.resolve("ZippedDirectory.zip");
    try (var zos = new ZipOutputStream(
                            new BufferedOutputStream(Files.newOutputStream(zipPath)))) {
                            
        Files.createDirectory(foldertozip);
        Files.createDirectory(foldertozip.resolve("emptydir"));
        Files.writeString(dirFile1, "Does this Java get you rev'd up or what?");
        Files.writeString(dirFile2, "Java Java Java ... Buz Buz Buz!");

        Files.walk(foldertozip).forEach(path -> {
            try {
                var reliativePath = inputDataDir.relativize(path);
                var file = path.toFile();
                if (file.isDirectory()) {
                    var files = file.listFiles();
                    if (files == null || files.length == 0) {
                        zos.putNextEntry(new ZipEntry(
                                reliativePath.toString() + File.separator));
                        zos.closeEntry();
                    }
                } else {
                    zos.putNextEntry(new ZipEntry(reliativePath.toString()));
                    zos.write(Files.readAllBytes(path));
                    zos.closeEntry();
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        });
    } catch(IOException e) {
        e.printStackTrace();
    }
}

Conclusión

En este artículo, he discutido y demostrado un enfoque moderno para trabajar con archivos zip en Java utilizando Java puro y sin bibliotecas de terceros. También puede notar que utilizo algunas características más modernas del lenguaje Java, como los paradigmas de programación funcional y la var palabra clave para el tipo de variables inferidas, así que asegúrese de usar al menos Java 10 cuando ejecute estos ejemplos.

Como siempre, gracias por leer y no dude en comentar o criticar a continuación.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad