java.util.zip – Recreando la estructura del directorio

Al tratar de comprimir un archivo usando el archivo java.util.zip tuve muchos problemas, la mayoría de los cuales resolví. Ahora que finalmente obtengo algo de salida, lucho por obtener la salida “correcta”. Tengo un archivo ODT extraído (el directorio sería más adecuado para una descripción) al que hice algunas modificaciones. Ahora quiero comprimir ese directorio para recrear la estructura del archivo ODT. Comprimir el directorio y cambiarle el nombre para que termine con .odt funciona bien, así que no debería haber ningún problema.

El principal problema es que pierdo la estructura interna del directorio. Todo se vuelve “plano” y no parece encontrar una manera de preservar la estructura original de varias capas. Agradecería algo de ayuda sobre esto ya que no puedo encontrar el problema.

Aquí están los fragmentos de código relevantes:

 ZipOutputStream out = new ZipOutputStream(new FileOutputStream( FILEPATH.substring(0, FILEPATH.lastIndexOf(SEPARATOR) + 1).concat("test.zip"))); compressDirectory(TEMPARCH, out); 

El SEPARATOR es el separador de archivos del sistema y FILEPATH es la FILEPATH archivo del ODT original que FILEPATH pero no he hecho aquí para fines de prueba. Simplemente escribo en un archivo test.zip en el mismo directorio.

 private void compressDirectory(String directory, ZipOutputStream out) throws IOException { File fileToCompress = new File(directory); // list contents. String[] contents = fileToCompress.list(); // iterate through directory and compress files. for(int i = 0; i  0) { out.write(data, 0, len); } out.flush(); out.closeEntry(); in.close(); } } } 

El directorio que contiene los archivos para comprimir está en algún lugar del espacio de usuario y no en el mismo directorio que el archivo resultante. Supongo que esto podría ser un problema, pero realmente no puedo ver cómo. También calculé que el problema podría estar en usar la misma stream para la salida, pero de nuevo no puedo ver cómo. Vi en algunos ejemplos y tutoriales que usan getPath() lugar de getName() pero cambiar eso me da un archivo zip vacío.

La clase URI es útil para trabajar con rutas relativas.

 File mydir = new File("C:\\mydir"); File myfile = new File("C:\\mydir\\path\\myfile.txt"); System.out.println(mydir.toURI().relativize(myfile.toURI()).getPath()); 

El código anterior emitirá la path/myfile.txt la cadena path/myfile.txt .

Para completar, aquí hay un método zip para archivar un directorio:

  public static void zip(File directory, File zipfile) throws IOException { URI base = directory.toURI(); Deque queue = new LinkedList(); queue.push(directory); OutputStream out = new FileOutputStream(zipfile); Closeable res = out; try { ZipOutputStream zout = new ZipOutputStream(out); res = zout; while (!queue.isEmpty()) { directory = queue.pop(); for (File kid : directory.listFiles()) { String name = base.relativize(kid.toURI()).getPath(); if (kid.isDirectory()) { queue.push(kid); name = name.endsWith("/") ? name : name + "/"; zout.putNextEntry(new ZipEntry(name)); } else { zout.putNextEntry(new ZipEntry(name)); copy(kid, zout); zout.closeEntry(); } } } } finally { res.close(); } } 

Este código no conserva las fechas y no estoy seguro de cómo reactjsría a cosas como enlaces simbólicos. No se intenta agregar entradas de directorio, por lo que los directorios vacíos no se incluirán.

El comando de unzip correspondiente:

  public static void unzip(File zipfile, File directory) throws IOException { ZipFile zfile = new ZipFile(zipfile); Enumeration entries = zfile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); File file = new File(directory, entry.getName()); if (entry.isDirectory()) { file.mkdirs(); } else { file.getParentFile().mkdirs(); InputStream in = zfile.getInputStream(entry); try { copy(in, file); } finally { in.close(); } } } } 

Métodos de utilidad en los que confían:

  private static void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; while (true) { int readCount = in.read(buffer); if (readCount < 0) { break; } out.write(buffer, 0, readCount); } } private static void copy(File file, OutputStream out) throws IOException { InputStream in = new FileInputStream(file); try { copy(in, out); } finally { in.close(); } } private static void copy(InputStream in, File file) throws IOException { OutputStream out = new FileOutputStream(file); try { copy(in, out); } finally { out.close(); } } 

El tamaño del buffer es completamente arbitrario.

Veo 2 problemas en tu código,

  1. No guarda la ruta del directorio por lo que no hay forma de recuperarla.
  2. En Windows, debe usar “/” como separador de ruta. A algunos progtwigs de descompresión no les gusta \.

Incluyo mi propia versión para su referencia. Usamos este para comprimir las fotos para descargar, así que funciona con varios progtwigs de descompresión. Conserva la estructura del directorio y las marcas de tiempo.

  public static void createZipFile(File srcDir, OutputStream out, boolean verbose) throws IOException { List fileList = listDirectory(srcDir); ZipOutputStream zout = new ZipOutputStream(out); zout.setLevel(9); zout.setComment("Zipper v1.2"); for (String fileName : fileList) { File file = new File(srcDir.getParent(), fileName); if (verbose) System.out.println(" adding: " + fileName); // Zip always use / as separator String zipName = fileName; if (File.separatorChar != '/') zipName = fileName.replace(File.separatorChar, '/'); ZipEntry ze; if (file.isFile()) { ze = new ZipEntry(zipName); ze.setTime(file.lastModified()); zout.putNextEntry(ze); FileInputStream fin = new FileInputStream(file); byte[] buffer = new byte[4096]; for (int n; (n = fin.read(buffer)) > 0;) zout.write(buffer, 0, n); fin.close(); } else { ze = new ZipEntry(zipName + '/'); ze.setTime(file.lastModified()); zout.putNextEntry(ze); } } zout.close(); } public static List listDirectory(File directory) throws IOException { Stack stack = new Stack(); List list = new ArrayList(); // If it's a file, just return itself if (directory.isFile()) { if (directory.canRead()) list.add(directory.getName()); return list; } // Traverse the directory in width-first manner, no-recursively String root = directory.getParent(); stack.push(directory.getName()); while (!stack.empty()) { String current = (String) stack.pop(); File curDir = new File(root, current); String[] fileList = curDir.list(); if (fileList != null) { for (String entry : fileList) { File f = new File(curDir, entry); if (f.isFile()) { if (f.canRead()) { list.add(current + File.separator + entry); } else { System.err.println("File " + f.getPath() + " is unreadable"); throw new IOException("Can't read file: " + f.getPath()); } } else if (f.isDirectory()) { list.add(current + File.separator + entry); stack.push(current + File.separator + f.getName()); } else { throw new IOException("Unknown entry: " + f.getPath()); } } } } return list; } } 

Simplemente ingrese a la fuente de java.util.zip.ZipEntry. Trata un ZipEntry como directorio si su nombre termina con caracteres “/”. Simplemente agregue el nombre del directorio con “/”. También debe eliminar el prefijo de la unidad para hacerlo relativo.

Verifique este ejemplo para comprimir solo los directorios vacíos,

http://bethecoder.com/applications/tutorials/showTutorials.action?tutorialId=Java_ZipUtilities_ZipEmptyDirectory

Siempre que pueda crear directorios vacíos y no vacíos en un archivo ZIP, la estructura de su directorio estará intacta.

Buena suerte.

Aquí hay otro ejemplo (recursivo) que también le permite incluir / excluir la carpeta que contiene el zip:

 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class ZipUtil { private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; public static void main(String[] args) throws Exception { zipFile("C:/tmp/demo", "C:/tmp/demo.zip", true); } public static void zipFile(String fileToZip, String zipFile, boolean excludeContainingFolder) throws IOException { ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile)); File srcFile = new File(fileToZip); if(excludeContainingFolder && srcFile.isDirectory()) { for(String fileName : srcFile.list()) { addToZip("", fileToZip + "/" + fileName, zipOut); } } else { addToZip("", fileToZip, zipOut); } zipOut.flush(); zipOut.close(); System.out.println("Successfully created " + zipFile); } private static void addToZip(String path, String srcFile, ZipOutputStream zipOut) throws IOException { File file = new File(srcFile); String filePath = "".equals(path) ? file.getName() : path + "/" + file.getName(); if (file.isDirectory()) { for (String fileName : file.list()) { addToZip(filePath, srcFile + "/" + fileName, zipOut); } } else { zipOut.putNextEntry(new ZipEntry(filePath)); FileInputStream in = new FileInputStream(srcFile); byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int len; while ((len = in.read(buffer)) != -1) { zipOut.write(buffer, 0, len); } in.close(); } } } 

Si no quiere molestarse en tratar con flujos de entrada de bytes, tamaños de búfer y otros detalles de bajo nivel. Puede usar las librerías Zip de Ant desde su código java (las dependencias maven se pueden encontrar aquí ). Aquí está ahora hago un zip que consiste en una lista de archivos y directorios:

 public static void createZip(File zipFile, List fileList) { Project project = new Project(); project.init(); Zip zip = new Zip(); zip.setDestFile(zipFile); zip.setProject(project); for(String relativePath : fileList) { //notwiglize the path (using commons-io, might want to null-check) String normalizedPath = FilenameUtils.normalize(relativePath); //create the file that will be used File fileToZip = new File(normalizedPath); if(fileToZip.isDirectory()) { ZipFileSet fileSet = new ZipFileSet(); fileSet.setDir(fileToZip); fileSet.setPrefix(fileToZip.getPath()); zip.addFileset(fileSet); } else { FileSet fileSet = new FileSet(); fileSet.setDir(new File(".")); fileSet.setIncludes(normalizedPath); zip.addFileset(fileSet); } } Target target = new Target(); target.setName("ziptarget"); target.addTask(zip); project.addTarget(target); project.executeTarget("ziptarget"); } 

Para comprimir el contenido de una carpeta y sus subcarpetas en Windows,

reemplazar,

 out.putNextEntry(new ZipEntry(files[i])); 

con

 out.putNextEntry(new ZipEntry(files[i]).replace(inFolder+"\\,"")); 

Me gustaría agregar una sugerencia / recordatorio aquí:

Si define que el directorio de salida es el mismo que el de entrada, querrá comparar el nombre de cada archivo con el nombre del archivo .zip de salida, para evitar comprimir el archivo dentro de sí mismo y generar un comportamiento no deseado. Espero que esto sea de alguna ayuda.

Este código cortado funciona para mí. No se necesita una biblioteca de terceros.

 public static void zipDir(final Path dirToZip, final Path out) { final Stack stackOfDirs = new Stack<>(); final Function, String> createPath = stack -> stack.stream().collect(Collectors.joining("/")) + "/"; try(final ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(out.toFile()))) { Files.walkFileTree(dirToZip, new FileVisitor() { @Override public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException { stackOfDirs.push(dir.toFile().getName()); final String path = createPath.apply(stackOfDirs); final ZipEntry zipEntry = new ZipEntry(path); zipOut.putNextEntry(zipEntry); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { final String path = String.format("%s%s", createPath.apply(stackOfDirs), file.toFile().getName()); final ZipEntry zipEntry = new ZipEntry(path); zipOut.putNextEntry(zipEntry); Files.copy(file, zipOut); zipOut.closeEntry(); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException { final StringWriter stringWriter = new StringWriter(); try(final PrintWriter printWriter = new PrintWriter(stringWriter)) { exc.printStackTrace(printWriter); System.err.printf("Failed visiting %s because of:\n %s\n", file.toFile().getAbsolutePath(), printWriter.toString()); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { stackOfDirs.pop(); return FileVisitResult.CONTINUE; } }); } catch (IOException e) { e.printStackTrace(); } }