Trabajar con archivos zip en Java

    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.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *