Comprimere e decomprimere in Java

1. Panoramica

In questo breve tutorial, discuteremo come comprimere un file in un archivio e come decomprimere l'archivio, il tutto utilizzando le librerie di base fornite da Java.

Queste librerie di base fanno parte del pacchetto java.util.zip , dove possiamo trovare tutte le utilità relative alla compressione e alla decompressione.

2. Comprimi un file

Diamo prima un'occhiata a una semplice operazione: zippare un singolo file.

Per il nostro esempio qui comprimeremo un file chiamato test1.txt in un file compressed.zip archiviato .

Ovviamente prima accediamo al file dal disco, diamo un'occhiata:

public class ZipFile { public static void main(String[] args) throws IOException { String sourceFile = "test1.txt"; FileOutputStream fos = new FileOutputStream("compressed.zip"); ZipOutputStream zipOut = new ZipOutputStream(fos); File fileToZip = new File(sourceFile); FileInputStream fis = new FileInputStream(fileToZip); ZipEntry zipEntry = new ZipEntry(fileToZip.getName()); zipOut.putNextEntry(zipEntry); byte[] bytes = new byte[1024]; int length; while((length = fis.read(bytes)) >= 0) { zipOut.write(bytes, 0, length); } zipOut.close(); fis.close(); fos.close(); } }

3. Zip più file

Successivamente, vediamo come comprimere più file in un unico file zip. Comprimeremo test1.txt e test2.txt in multiCompressed.zip :

public class ZipMultipleFiles { public static void main(String[] args) throws IOException { List srcFiles = Arrays.asList("test1.txt", "test2.txt"); FileOutputStream fos = new FileOutputStream("multiCompressed.zip"); ZipOutputStream zipOut = new ZipOutputStream(fos); for (String srcFile : srcFiles) { File fileToZip = new File(srcFile); FileInputStream fis = new FileInputStream(fileToZip); ZipEntry zipEntry = new ZipEntry(fileToZip.getName()); zipOut.putNextEntry(zipEntry); byte[] bytes = new byte[1024]; int length; while((length = fis.read(bytes)) >= 0) { zipOut.write(bytes, 0, length); } fis.close(); } zipOut.close(); fos.close(); } }

4. Comprimere una directory

Ora, discutiamo su come comprimere un'intera directory. Directory zipTest in dirCompressed.zip :

public class ZipDirectory { public static void main(String[] args) throws IOException { String sourceFile = "zipTest"; FileOutputStream fos = new FileOutputStream("dirCompressed.zip"); ZipOutputStream zipOut = new ZipOutputStream(fos); File fileToZip = new File(sourceFile); zipFile(fileToZip, fileToZip.getName(), zipOut); zipOut.close(); fos.close(); } private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException { if (fileToZip.isHidden()) { return; } if (fileToZip.isDirectory()) { if (fileName.endsWith("/")) { zipOut.putNextEntry(new ZipEntry(fileName)); zipOut.closeEntry(); } else { zipOut.putNextEntry(new ZipEntry(fileName + "/")); zipOut.closeEntry(); } File[] children = fileToZip.listFiles(); for (File childFile : children) { zipFile(childFile, fileName + "/" + childFile.getName(), zipOut); } return; } FileInputStream fis = new FileInputStream(fileToZip); ZipEntry zipEntry = new ZipEntry(fileName); zipOut.putNextEntry(zipEntry); byte[] bytes = new byte[1024]; int length; while ((length = fis.read(bytes)) >= 0) { zipOut.write(bytes, 0, length); } fis.close(); } }

Nota che:

  • Per comprimere le sottodirectory, le iteriamo ricorsivamente.
  • Ogni volta che troviamo una directory, aggiungiamo il suo nome al nome ZipEntry dei discendenti per salvare la gerarchia.
  • Creiamo anche una voce di directory per ogni directory vuota

5. Decomprimere un archivio

Ora decomprimiamo un archivio ed estraiamo il suo contenuto.

Per questo esempio, decomprimeremo compressed.zip in una nuova cartella denominata unzipTest .

Diamo un'occhiata:

public class UnzipFile { public static void main(String[] args) throws IOException { String fileZip = "src/main/resources/unzipTest/compressed.zip"; File destDir = new File("src/main/resources/unzipTest"); byte[] buffer = new byte[1024]; ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip)); ZipEntry zipEntry = zis.getNextEntry(); while (zipEntry != null) { // ... } zis.closeEntry(); zis.close(); } }

All'interno del ciclo while , itereremo su ogni ZipEntry e prima verificheremo se si tratta di una directory . Se lo è, creeremo la directory utilizzando il metodo mkdirs () ; altrimenti, continueremo con la creazione del file:

while (zipEntry != null) { File newFile = newFile(destDir, zipEntry); if (zipEntry.isDirectory()) { if (!newFile.isDirectory() && !newFile.mkdirs()) { throw new IOException("Failed to create directory " + newFile); } } else { // fix for Windows-created archives File parent = newFile.getParentFile(); if (!parent.isDirectory() && !parent.mkdirs()) { throw new IOException("Failed to create directory " + parent); } // write file content FileOutputStream fos = new FileOutputStream(newFile); int len; while ((len = zis.read(buffer)) > 0) { fos.write(buffer, 0, len); } fos.close(); } zipEntry = zis.getNextEntry(); }

Una nota qui è che sul ramo else , stiamo anche controllando prima se la directory padre del file esiste. Ciò è necessario per gli archivi creati su Windows, dove le directory principali non hanno una voce corrispondente nel file zip.

Un altro punto chiave può essere visto nel metodo newFile () :

public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { File destFile = new File(destinationDir, zipEntry.getName()); String destDirPath = destinationDir.getCanonicalPath(); String destFilePath = destFile.getCanonicalPath(); if (!destFilePath.startsWith(destDirPath + File.separator)) { throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); } return destFile; }

Questo metodo impedisce la scrittura di file nel file system all'esterno della cartella di destinazione. Questa vulnerabilità si chiama Zip Slip e puoi leggere di più al riguardo qui.

6. Conclusione

Questo tutorial ha illustrato come possiamo usare le librerie Java per le operazioni di zippare e decomprimere file.

L'implementazione di questi esempi può essere trovata su GitHub.