it-swarm.com.de

java.util.Zip - Verzeichnisstruktur neu erstellen

Beim Versuch, ein Archiv mit dem Java.util.Zip zu komprimieren, bin ich auf viele Probleme gestoßen, von denen ich die meisten gelöst habe. Nun, da ich endlich etwas Output bekomme, habe ich Schwierigkeiten mit dem "richtigen" Output. Ich habe eine extrahierte ODT-Datei (Verzeichnis würde eher eine Beschreibung sein), an der ich einige Änderungen vorgenommen habe. Jetzt möchte ich dieses Verzeichnis komprimieren, um die ODT-Dateistruktur neu zu erstellen. Das Zippen des Verzeichnisses und das Umbenennen in .odt funktioniert einwandfrei. Es sollte also kein Problem sein.

Das Hauptproblem ist, dass ich die interne Struktur des Verzeichnisses verliere. Alles wird "flach" und ich scheine keinen Weg zu finden, um die ursprüngliche mehrschichtige Struktur zu erhalten. Ich wäre dankbar, da ich das Problem offenbar nicht finden kann.

Hier sind die relevanten Codeausschnitte:

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

Die SEPARATOR ist der Systemdateiseparator und die FILEPATH ist der Dateipfad des ursprünglichen ODTs, den ich außer Kraft setzen werde, aber zu Testzwecken hier nicht gemacht habe. Ich schreibe einfach in eine test.Zip-Datei im selben Verzeichnis.

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 < contents.length; i++)
    {
        File f = new File(directory, contents[i]);
        // testing type. directories and files have to be treated separately.
        if(f.isDirectory())
        {
            // add empty directory
            out.putNextEntry(new ZipEntry(f.getName() + SEPARATOR));
            // initiate recursive call
            compressDirectory(f.getPath(), out);
            // continue the iteration
            continue;
        }else{
             // prepare stream to read file.
             FileInputStream in = new FileInputStream(f);
             // create ZipEntry and add to outputting stream.
             out.putNextEntry(new ZipEntry(f.getName()));
             // write the data.
             int len;
             while((len = in.read(data)) > 0)
             {
                 out.write(data, 0, len);
             }
             out.flush();
             out.closeEntry();
             in.close();
         }
     }
 }

Das Verzeichnis, das die Dateien für Zip enthält, befindet sich irgendwo im Benutzerbereich und nicht im selben Verzeichnis wie die resultierende Datei. Ich gehe davon aus, dass dies Probleme verursachen könnte, aber ich kann nicht wirklich sehen, wie. Ich habe auch gedacht, dass das Problem darin liegen könnte, denselben Stream für die Ausgabe zu verwenden, aber wieder kann ich nicht sehen, wie. Ich habe in einigen Beispielen und Tutorials gesehen, dass sie getPath() anstelle von getName() verwenden, aber wenn ich das ändern würde, bekomme ich eine leere Zip-Datei.

38
Eric

Die URI -Klasse ist nützlich für das Arbeiten mit relativen Pfaden.

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

Der obige Code wird den String path/myfile.txt ausgeben.

Der Vollständigkeit halber sei hier eine Zip-Methode zum Archivieren eines Verzeichnisses angegeben:

  public static void Zip(File directory, File zipfile) throws IOException {
    URI base = directory.toURI();
    Deque<File> queue = new LinkedList<File>();
    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();
    }
  }

Durch diesen Code werden keine Daten beibehalten und ich bin nicht sicher, wie er auf Dinge wie Symlinks reagiert. Es wird nicht versucht, Verzeichniseinträge hinzuzufügen, so dass leere Verzeichnisse nicht eingeschlossen werden.

Der entsprechende unzip Befehl:

  public static void unzip(File zipfile, File directory) throws IOException {
    ZipFile zfile = new ZipFile(zipfile);
    Enumeration<? extends ZipEntry> 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();
        }
      }
    }
  }

Gebrauchsmethoden, auf die sie sich verlassen:

  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();
    }
  }

Die Puffergröße ist völlig willkürlich.

91
McDowell

Ich sehe 2 Probleme in Ihrem Code,

  1. Sie speichern den Verzeichnispfad nicht, daher gibt es keine Möglichkeit, ihn zurückzubekommen.
  2. Unter Windows müssen Sie "/" als Pfadtrennzeichen verwenden. Einige Entpack-Programme mögen nicht \.

Ich lege meine eigene Version als Referenz bei. Wir verwenden diese, um Fotos zu komprimieren, um sie herunterzuladen. Die Verzeichnisstruktur und die Zeitstempel bleiben erhalten. 

  public static void createZipFile(File srcDir, OutputStream out,
   boolean verbose) throws IOException {

  List<String> 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<String> listDirectory(File directory)
   throws IOException {

  Stack<String> stack = new Stack<String>();
  List<String> list = new ArrayList<String>();

  // 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;
 }
}
7
ZZ Coder

Gehen Sie einfach durch die Quelle von Java.util.Zip.ZipEntry. Ein ZipEntry wird als Verzeichnis behandelt, wenn sein Name mit "/" - Zeichen endet. Suffixe den Verzeichnisnamen einfach mit "/".". Außerdem müssen Sie das Laufwerkpräfix entfernen, um es relativ zu machen.

Überprüfen Sie dieses Beispiel, um nur die leeren Verzeichnisse zu packen. 

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

Solange Sie in der Zip-Datei sowohl leere als auch nicht leere Verzeichnisse erstellen können, bleibt Ihre Verzeichnisstruktur erhalten. 

Viel Glück.

4
user769087

Hier ist ein weiteres Beispiel (rekursiv), mit dem Sie auch den enthaltenen Ordner aus der Zip-Datei ein- bzw. ausschließen können:

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();
    }
  }
}
3
fcarriedo

Wenn Sie sich nicht mit Byte-Eingabeströmen, Puffergrößen und anderen Low-Level-Details befassen möchten. Sie können Ant's Zip-Bibliotheken aus Ihrem Java-Code verwenden (Maven-Abhängigkeiten finden Sie hier ). Hier ist jetzt ein Zip, der eine Liste von Dateien und Verzeichnissen enthält:

public static void createZip(File zipFile, List<String> fileList) {

    Project project = new Project();
    project.init();

    Zip zip = new Zip();
    Zip.setDestFile(zipFile);
    Zip.setProject(project);

    for(String relativePath : fileList) {

        //noramalize 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");
}
2

Ich möchte hier einen Vorschlag/eine Erinnerung einfügen:

Wenn Sie festlegen, dass das Ausgabeverzeichnis mit dem Eingabeverzeichnis identisch ist, müssen Sie den Dateinamen jeder Datei mit dem Dateinamen der Ausgabedatei .Zip vergleichen, um zu vermeiden, dass die Datei in sich komprimiert wird, was zu unerwünschtem Verhalten führt. Hoffe, das ist eine Hilfe. 

1
Luca Bezerra

So komprimieren Sie den Inhalt eines Ordners und seiner Unterordner in Windows

ersetzen, 

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

mit

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

Dieser Code-Ausschnitt funktioniert für mich. Keine Bibliothek von Drittanbietern erforderlich.

public static void zipDir(final Path dirToZip, final Path out) {
    final Stack<String> stackOfDirs = new Stack<>();
    final Function<Stack<String>, String> createPath = stack -> stack.stream().collect(Collectors.joining("/")) + "/";
    try(final ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(out.toFile()))) {
        Files.walkFileTree(dirToZip, new FileVisitor<Path>() {

            @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();
    }
}
0