【问题标题】:java.util.zip - Recreating directory structurejava.util.zip - 重新创建目录结构
【发布时间】:2010-11-26 19:32:30
【问题描述】:

在尝试使用 java.util.zip 压缩存档时,我遇到了很多问题,其中大部分我都解决了。现在我终于得到了一些输出,我很难获得“正确”的输出。我有一个提取的 ODT 文件(目录更适合描述),我对其进行了一些修改。现在我想压缩该目录以重新创建 ODT 文件结构。压缩目录并将其重命名为以 .odt 结尾可以正常工作,因此应该没有问题。

主要问题是我丢失了目录的内部结构。一切都变得“平坦”,我似乎没有找到保留原始多层结构的方法。我将不胜感激,因为我似乎无法找到问题所在。

这里是相关代码sn-ps:

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

SEPARATOR 是系统文件分隔符,FILEPATH 是原始 ODT 的文件路径,我将覆盖它,但出于测试目的,这里没有这样做。我只是在同一目录中写入一个 test.zip 文件。

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

包含要压缩的文件的目录位于用户空间中的某个位置,而不是与生成的文件位于同一目录中。我认为这可能会很麻烦,但我真的不知道怎么做。我还认为问题可能在于使用相同的流进行输出,但我又看不出如何。我在一些示例和教程中看到他们使用getPath() 而不是getName(),但更改它会给我一个空的 zip 文件。

【问题讨论】:

    标签: java directory zip structure


    【解决方案1】:

    URI 类对于处理相对路径很有用。

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

    以上代码将发出字符串path/myfile.txt

    为了完整起见,这里是一个用于归档目录的zip 方法:

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

    此代码不保留日期,我不确定它会如何对符号链接之类的内容做出反应。不会尝试添加目录条目,因此不会包含空目录。

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

    他们所依赖的实用方法:

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

    缓冲区大小完全是任意的。

    【讨论】:

    • 非常感谢!唉,我看不到原始目录格式是如何与您的压缩代码一起保存的。在我使用的 ODT 中有空目录。据我了解您的代码,永远不会创建这些目录。我可能错过了什么吗?
    • 目录是名称以/ 结尾的空条目。我已经修改了代码。
    • 我调整了您的代码结构并放弃了递归调用。我认为这是错误的看待这个问题的方式。代码运行平稳,有一个例外;即使它们不为空,它也会将空文件夹添加到大多数子目录。我发现删除以下行可以解决问题: name = name.endsWith("/") ?名称:名称+“/”;我怀疑通过附加“\”添加目录时,也会在其中创建一个空文件夹。通过简单地让 ZipEntries 负责结构构建,一切看起来都很好。谢谢大家的帮助!
    • 潜在 NPE:如果目录为空,directory.listFiles() 将返回 null!
    • @AxelFontaine 如果目录为空,它将返回一个空目录 - doc - 但在某些情况下可以返回 null,因此在这种情况下最好抛出 IOException .
    【解决方案2】:

    我在您的代码中发现了 2 个问题,

    1. 您没有保存目录路径,因此无法取回它。
    2. 在 Windows 上,您需要使用“/”作为路径分隔符。一些解压程序不喜欢\。

    我包含我自己的版本供您参考。我们使用这个来压缩照片以下载,因此它可以与各种解压缩程序一起使用。它保留了目录结构和时间戳。

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

    【讨论】:

    • 感谢您的贡献。我很感谢您的代码示例,但我不确定它将如何帮助我找到代码中的错误;对函数的初始调用是完整的目录路径,然后将在函数中传递;其次;我的 SEPARATOR 常量使用 System.getProperty("file.separator") 初始化,这将为我提供操作系统默认文件分隔符。我永远不会硬编码分隔符,因为它假定您的代码只会部署在给定的操作系统上。
    • 不要在 ZIP 中使用 File.separator。根据规范,分隔符必须是“/”。如果您使用的是 Windows,则必须以“D:\dir\subdir\file”打开文件,但 ZIP 条目必须为“dir/subdir/file”。
    • 我明白了。感谢您指出这一点。不知道ZIP这么挑剔! :)
    【解决方案3】:

    只需浏览 java.util.zip.ZipEntry 的源代码即可。如果 ZipEntry 的名称以“/”字符结尾,它会将其视为目录。只需在目录名称后加上“/”即可。 您还需要删除驱动器前缀以使其相对。

    查看此示例以仅压缩空目录,

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

    只要您能够在 ZIP 文件中创建空目录和非空目录,您的目录结构就完好无损。

    祝你好运。

    【讨论】:

      【解决方案4】:

      这是另一个示例(递归),它还允许您从 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();
          }
        }
      }
      

      【讨论】:

        【解决方案5】:

        如果您不想费心处理字节输入流、缓冲区大小和其他低级细节。您可以从您的 java 代码中使用 Ant 的 Zip 库(可以找到 maven 依赖项here)。现在我制作了一个包含文件和目录列表的 zip:

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

        【讨论】:

          【解决方案6】:

          在 Windows 中压缩文件夹及其子文件夹的内容,

          替换,

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

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

          【讨论】:

            【解决方案7】:

            我想在这里添加一个建议/提醒:

            如果您将输出目录定义为与输入目录相同,您需要将每个文件的名称与输出 .zip 的文件名进行比较,以避免压缩文件本身,从而产生一些不需要的行为。希望这有任何帮助。

            【讨论】:

              【解决方案8】:

              这段代码对我有用。无需第三方库。

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-07-24
                • 1970-01-01
                • 1970-01-01
                • 2018-03-27
                • 2012-07-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多