java.util.zip - 重新创建目录结构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1399126/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 11:39:33  来源:igfitidea点击:

java.util.zip - Recreating directory structure

javadirectoryzipstructure

提问by Eric

While trying to zip an archive using the java.util.zipI ran into a lot of problems most of which I solved. Now that I finally get some output I struggle with getting the "right" output. I have an extracted ODT file (directory would be more fitting a description) to which I did some modifications. Now I want to compress that directory as to recreate the ODT file structure. Zipping the directory and renaming it to end with .odt works fine so there should be no problem.

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

The main problem is that I lose the internal structure of the directory. Everything becomes "flat" and I do not seem to find a way to preserve the original multi-layered structure. I would appreciate some help on this as I can not seem to find the problem.

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

Here are the relevant code snippets:

以下是相关的代码片段:

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

The SEPARATORis the system file separator and the FILEPATHis the filepath of the original ODT which I will override but have not done here for testing purposes. I simply write to a test.zip file in the same directory.

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

The directory that contains the files to zip is somewhere in the user space and not in the same directory as the resulting file. I assume this could be trouble but I can not really see how. Also I figured that the problem could be in using the same stream for outputting but again I can not see how. I saw in some examples and tutorials that they use getPath()instead of getName()but changing that gives me an empty zip file.

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

采纳答案by McDowell

The URIclass is useful for working with relative paths.

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

The above code will emit the string path/myfile.txt.

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

For completeness, here is a zipmethod for archiving a directory:

为了完整起见,这里是一种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();
    }
  }

This code makes doesn't preserve dates and I'm not sure how it would react to stuff like symlinks. No attempt is made to add directory entries, so empty directories would not be included.

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

The corresponding unzipcommand:

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

Utility methods on which they rely:

他们依赖的实用方法:

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

The buffer size is entirely arbitrary.

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

回答by ZZ Coder

I see 2 problems in your code,

我在您的代码中看到 2 个问题,

  1. You don't save the directory path so there is no way to get it back.
  2. On Windows, you need to use "/" as path separator. Some unzip program doesn't like \.
  1. 您不保存目录路径,因此无法将其取回。
  2. 在 Windows 上,您需要使用“/”作为路径分隔符。一些解压缩程序不喜欢\。

I include my own version for your reference. We use this one to zip up photos to download so it works with various unzip programs. It preserves the directory structure and timestamps.

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

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

回答by user769087

Just go through the source of java.util.zip.ZipEntry. It treats a ZipEntry as directory if its name ends with "/" characters. Just suffix the directory name with "/". Also you need to remove the drive prefix to make it relative.

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

Check this example for zipping just the empty directories,

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

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

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

As long as you are able to create both empty & non-empty directories in ZIP file, your directory structure is intact.

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

Good luck.

祝你好运。

回答by Luca Bezerra

I would like to add a suggestion/reminder in here:

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

If you define the output directory being the same as the input one, you'll want to compare each file's name with the output .zip's file name, in order to avoid compressing the file inside itself, generating some unwanted behaviour. Hope this is of any help.

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

回答by Geo

To zip contents of a folder and its subfolders in Windows,

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

replace,

代替,

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

with

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

回答by fcarriedo

Here is another example (recursive) which also lets you include/exclude the containing folder form the zip:

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

回答by ChoppyTheLumberHyman

If you don't want to bother dealing with byte input streams, buffer sizes, and other low level details. You can use Ant's Zip libraries from your java code (maven dependencies can be found here). Here's now I make a zip consisting a list of files & directories:

如果您不想费心处理字节输入流、缓冲区大小和其他低级细节。您可以从 Java 代码中使用 Ant 的 Zip 库(可以在此处找到 Maven 依赖项)。现在我制作了一个包含文件和目录列表的 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");
}

回答by Alessandro Giusa

This code snipped works for me. No third-party library needed.

这段代码剪下来对我有用。不需要第三方库。

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