2009-09-09 39 views
35

Trong khi cố gắng nén lưu trữ bằng cách sử dụng java.util.zip Tôi gặp phải rất nhiều sự cố mà hầu hết tôi đã giải quyết. Bây giờ tôi cuối cùng đã nhận được một số đầu ra tôi đấu tranh với việc sản xuất "đúng". Tôi có một tệp ODT được trích xuất (thư mục sẽ phù hợp hơn với mô tả) mà tôi đã thực hiện một số sửa đổi. Bây giờ tôi muốn nén thư mục đó để tạo lại cấu trúc tệp ODT. Nén thư mục và đổi tên thư mục để kết thúc bằng .odt hoạt động tốt nên không có vấn đề gì.java.util.zip - Tái cấu trúc thư mục

Vấn đề chính là tôi mất cấu trúc bên trong của thư mục. Mọi thứ trở nên "phẳng" và tôi dường như không tìm được cách để bảo tồn cấu trúc nhiều lớp ban đầu. Tôi sẽ đánh giá cao một số trợ giúp về điều này vì tôi dường như không thể tìm thấy vấn đề.

Dưới đây là các đoạn mã liên quan:

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

Các SEPARATOR là file hệ thống tách và FILEPATH là filepath của ODT gốc mà tôi sẽ ghi đè lên nhưng chưa thực hiện ở đây cho mục đích thử nghiệm. Tôi chỉ cần viết vào một tệp test.zip trong cùng một thư mục.

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

Thư mục chứa tệp cần nén ở đâu đó trong không gian người dùng và không nằm trong cùng thư mục với tệp kết quả. Tôi cho rằng điều này có thể gây rắc rối nhưng tôi không thể thực sự thấy như thế nào. Ngoài ra tôi thấy rằng vấn đề có thể được sử dụng trong cùng một dòng để xuất nhưng một lần nữa tôi không thể nhìn thấy như thế nào. Tôi đã thấy trong một số ví dụ và hướng dẫn mà họ sử dụng getPath() thay vì getName() nhưng thay đổi cung cấp cho tôi một tệp zip trống.

Trả lời

87

Lớp URI hữu ích khi làm việc với đường dẫn tương đối.

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

Mã trên sẽ phát ra chuỗi path/myfile.txt.

Để hoàn chỉnh, đây là một phương pháp zip để lưu trữ một thư mục:

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

Mã này làm cho không giữ gìn ngày và tôi không chắc chắn làm thế nào nó sẽ phản ứng với những thứ như liên kết tượng trưng. Không có nỗ lực nào được thực hiện để thêm các mục nhập thư mục, vì vậy các thư mục trống sẽ không được bao gồm.

Các unzip lệnh tương ứng:

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

phương pháp Utility mà trên đó họ dựa:

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

Kích thước bộ đệm là hoàn toàn tùy ý.

+0

Cảm ơn bạn rất nhiều! Than ôi tôi không thể thấy cách định dạng thư mục ban đầu được giữ với mã nén của bạn. Trong ODT tôi sử dụng có các thư mục trống. Theo như tôi hiểu mã của bạn, những thư mục đó sẽ không bao giờ được tạo ra. Tôi có lẽ thiếu một cái gì đó? – Eric

+2

Thư mục là các mục trống với tên kết thúc bằng '/'. Tôi đã thay đổi mã. – McDowell

+0

Tôi đã điều chỉnh cấu trúc mã của bạn và bỏ qua các cuộc gọi đệ quy. Tôi nghĩ đó là một cách sai lầm khi nhìn vào điều này. Mã chạy trơn tru với một ngoại lệ; nó thêm các thư mục rỗng vào hầu hết các thư mục con ngay cả khi chúng không rỗng. Tôi thấy rằng việc xóa dòng sau giải quyết được vấn đề: name = name.endsWith ("/")? name: name + "/"; Tôi nghi ngờ rằng khi thêm một thư mục bằng cách thêm "\" một thư mục cũng tạo ra một thư mục trống bên trong. Bằng cách đơn giản cho phép ZipEntries quản lý cấu trúc, mọi thứ có vẻ ổn. Cảm ơn mọi sự giúp đỡ của bạn! – Eric

7

tôi thấy 2 vấn đề trong mã của bạn,

  1. Bạn không lưu đường dẫn thư mục để không có cách nào để có được nó trở lại.
  2. Trên Windows, bạn cần phải sử dụng "/" làm dấu tách đường dẫn. Một số chương trình giải nén không thích \.

Tôi bao gồm phiên bản của riêng tôi để bạn tham khảo. Chúng tôi sử dụng tệp này để nén ảnh để tải xuống để nó hoạt động với nhiều chương trình giải nén khác nhau.Nó bảo tồn cấu trúc thư mục và dấu thời gian.

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

Cảm ơn sự đóng góp của bạn. Tôi biết ơn ví dụ mã của bạn nhưng tôi không chắc chắn làm thế nào nó sẽ giúp tôi tìm thấy lỗi trong mã của tôi như; lệnh gọi ban đầu cho hàm là đường dẫn thư mục đầy đủ, sau đó sẽ được truyền lại trong hàm và thứ hai; hằng số SEPARATOR của tôi được khởi tạo với System.getProperty ("file.separator") sẽ cung cấp cho tôi dấu phân tách tệp mặc định của hệ điều hành. Tôi sẽ không bao giờ mã hóa một dấu phân tách vì giả định rằng mã của bạn sẽ chỉ được triển khai trên một hệ điều hành đã cho. – Eric

+4

Không sử dụng File.separator trong ZIP. Dấu phân tách phải là "/" theo thông số. Nếu bạn đang sử dụng Windows, bạn phải mở tệp dưới dạng "D: \ dir \ subdir \ file" nhưng mục nhập ZIP phải là "dir/subdir/file". –

+1

Tôi hiểu. Cám ơn bạn vì đã chỉ ra điều này. Không biết rằng ZIP đã rất cầu kỳ! :) – Eric

4

Chỉ cần xem qua nguồn java.util.zip.ZipEntry. Nó xử lý một ZipEntry làm thư mục nếu tên của nó kết thúc bằng ký tự "/". Chỉ cần kết thúc tên thư mục với "/". Ngoài ra, bạn cần phải loại bỏ tiền tố ổ đĩa để làm cho nó tương đối.

Kiểm tra ví dụ này cho nén chỉ các thư mục rỗng,

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

Chừng nào bạn có thể tạo ra cả & thư mục không rỗng trống trong tập tin ZIP, cấu trúc thư mục của bạn là còn nguyên vẹn.

Chúc may mắn.

1

Tôi muốn thêm một gợi ý/lời nhắc nhở ở đây:

Nếu bạn xác định thư mục đầu ra là giống như đầu vào một, bạn sẽ muốn so sánh tên của mỗi tập tin với tên file .zip đầu ra của , để tránh việc nén tệp bên trong, tạo ra một số hành vi không mong muốn. Hy vọng điều này là giúp đỡ bất kỳ.

1

Để nén nội dung của một thư mục và thư mục con của nó trong Windows,

thay thế,

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

với

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

Dưới đây là một ví dụ (đệ quy) mà còn cho phép bạn bao gồm/loại trừ thư mục chứa thư mục 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(); 
    } 
    } 
} 
2

Nếu bạn không muốn bận tâm xử lý luồng đầu vào byte, kích thước bộ đệm và các chi tiết mức thấp khác. Bạn có thể sử dụng thư viện Zip của Ant từ mã java của bạn (phụ thuộc maven có thể được tìm thấy here). Đây là bây giờ tôi tạo một zip bao gồm một danh sách các tập tin & thư mục:

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

Đoạn mã này hoạt động cho tôi. Không cần thư viện của bên thứ ba.

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