2009-09-09 7 views
35

Beim Versuch, ein Archiv unter Verwendung der java.util.zip zu zippen, stieß ich auf viele Probleme, von denen ich die meisten löste. Jetzt, wo ich endlich einen Output bekomme, habe ich Probleme mit der "richtigen" Ausgabe. Ich habe eine extrahierte ODT-Datei (Verzeichnis wäre passender eine Beschreibung), 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 mit .odt funktioniert einwandfrei, es sollte also kein Problem geben.java.util.zip - Verzeichnisstruktur neu erstellen

Das Hauptproblem ist, dass ich die interne Struktur des Verzeichnisses verliere. Alles wird "flach" und ich finde keinen Weg, die ursprüngliche vielschichtige Struktur zu erhalten. Ich würde mich über einige Hilfe freuen, da ich das Problem nicht zu finden scheint.

Hier sind die entsprechenden Code-Schnipsel:

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

Die SEPARATOR ist die Systemdatei Separator und die FILEPATH ist die Filepath des ursprünglichen ODT, die ich überschreiben, aber noch nicht hier zu Testzwecken durchgeführt. 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 enthält, ist irgendwo im Benutzerraum zip und nicht im selben Verzeichnis wie die resultierenden Datei. Ich nehme an, das könnte Ärger sein, aber ich kann nicht wirklich sehen, wie. Ich dachte auch, dass das Problem darin bestehen könnte, den gleichen Stream für die Ausgabe zu verwenden, aber ich kann nicht sehen, wie. Ich sah in einigen Beispielen und Tutorials, dass sie getPath() anstelle von getName() verwenden, aber das Ändern gibt mir eine leere Zip-Datei.

Antwort

87

Die Klasse URI ist nützlich für die Arbeit 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 die Zeichenfolge path/myfile.txt ausgeben.

Für Vollständigkeit, hier ist ein zip Verfahren zur Archivierung eines Verzeichnisses:

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

Dieser Code macht Daten nicht erhalten und ich bin nicht sicher, wie es Sachen wie Symlinks reagieren würde. Es wird nicht versucht, Verzeichniseinträge hinzuzufügen, daher werden leere Verzeichnisse nicht eingeschlossen.

Der entsprechende Befehl unzip:

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

Hilfsmethoden auf die sie sich stützen:

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 völlig willkürlich ist.

+0

Vielen Dank! Leider kann ich nicht sehen, wie das ursprüngliche Verzeichnisformat mit Ihrem Komprimierungscode beibehalten wird. Im ODT verwende ich leere Verzeichnisse. Soweit ich Ihren Code verstehe, werden diese Verzeichnisse niemals erstellt. Vermisse ich vielleicht etwas? – Eric

+2

Verzeichnisse sind leere Einträge mit einem Namen, der mit '/' endet. Ich habe den Code geändert. – McDowell

+0

Ich habe die Struktur Ihres Codes angepasst und die rekursiven Aufrufe aufgegeben. Ich denke, es war der falsche Weg, dies zu betrachten. Der Code läuft reibungslos mit einer Ausnahme; Es fügt den meisten untergeordneten Verzeichnissen leere Ordner hinzu, auch wenn sie nicht leer sind. Ich habe festgestellt, dass das Entfernen der folgenden Zeile das Problem löst: name = name.endsWith ("/")? Name: Name + "/"; Ich vermute, dass beim Hinzufügen eines Verzeichnisses durch Anhängen des "\" auch ein leerer Ordner darin erstellt wird. Indem man einfach die ZipEntries auf den Aufbau der Struktur achtet, scheint alles in Ordnung zu sein. Danke Ihnen allen für Ihre Hilfe! – Eric

7

Ich sehe zwei Probleme in Ihrem Code,

  1. Sie können den Verzeichnispfad speichern, so dass es keine Möglichkeit gibt es zurück zu bekommen.
  2. Unter Windows müssen Sie "/" als Pfadtrennzeichen verwenden. Einige entpacken Programm nicht wie \.

Ich schließe meine eigene Version für Ihre Referenz ein. Wir verwenden diese, um Fotos zum Herunterladen zu komprimieren, so dass es mit verschiedenen Unzip-Programmen funktioniert.Es behält die Verzeichnisstruktur und Zeitstempel bei.

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; 
} 
} 
+0

Vielen Dank für Ihren Beitrag. Ich bin dankbar für Ihr Codebeispiel, aber ich bin mir nicht sicher, wie es mir helfen wird, den Fehler in meinem Code zu finden. der erste Aufruf der Funktion erfolgt mit dem vollständigen Verzeichnispfad, der dann in der Funktion übergeben wird und zweitens; Meine SEPARATOR-Konstante wird mit der System.getProperty ("file.separator") initialisiert, die mir das Standard-Dateitrennzeichen des Betriebssystems gibt. Ich würde niemals ein Trennzeichen hart codieren, da das voraussetzt, dass Ihr Code nur auf einem bestimmten Betriebssystem bereitgestellt wird. – Eric

+4

Verwenden Sie File.separator nicht in ZIP. Das Trennzeichen muss laut Spezifikation "/" sein. Wenn Sie unter Windows arbeiten, müssen Sie die Datei als "D: \ Verzeichnis \ Unterverzeichnis \ Datei" öffnen, die ZIP-Datei muss jedoch "Verzeichnis/Unterverzeichnis/Datei" sein. –

+1

Ich sehe. Vielen Dank, dass Sie darauf hingewiesen haben. Wusste nicht, dass ZIP so wählerisch war! :) – Eric

4

Gehen Sie einfach durch die Quelle von java.util.zip.ZipEntry. Es behandelt einen ZipEntry als Verzeichnis, wenn sein Name mit "/" Zeichen endet. Suffix einfach den Verzeichnisnamen mit "/". Sie müssen auch das Laufwerkspräfix entfernen, um es relativ zu machen.

prüft dieses Beispiel nur für die leeren Verzeichnisse zu zippen,

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

Solange Sie beide erstellen leer sind in der Lage & nicht-leere Verzeichnisse in ZIP-Datei, ist die Verzeichnisstruktur intakt.

Viel Glück.

1

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

Wenn Sie das Ausgabeverzeichnis die gleiche wie die Eingangs definieren eins ist, werden Sie wollen die Namen der einzelnen Datei vergleichen mit dem Dateinamen der Ausgabe .zip , um zu vermeiden, dass die Datei in sich selbst komprimiert wird, was zu unerwünschtem Verhalten führt. Hoffe, das hilft irgendwie.

1

Zum Inhalt eines Ordners und dessen Unterordner in Windows zip,

ersetzen,

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

mit

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

Hier ist ein weiteres Beispiel (rekursiv), die können Sie auch include/Schließen Sie den enthaltenen Ordner aus der ZIP-Datei aus:

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

Wenn Sie sich nicht mit Byte-Eingangsströmen, Puffergrößen und anderen Details auf niedriger Ebene beschäftigen möchten. Sie können die Zip-Bibliotheken von Ant aus Ihrem Java-Code verwenden (Maven-Abhängigkeiten können gefunden werden here). Hier ist jetzt ich eine Zip machen eine Liste von Dateien aus & Verzeichnisse:

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

Dieser Code funktioniert bei mir snipped. Keine Drittanbieter-Bibliothek benötigt.

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