java.util.zip – 重新创build目录结构

在尝试使用java.util.zip压缩压缩文件时,遇到了很多我解决的问题。 现在,我终于得到了一些输出,我很难得到“正确的”输出。 我有一个提取的ODT文件(目录会更适合描述),我做了一些修改。 现在我想压缩该目录以重新创buildODT文件结构。 压缩目录并重命名以.odt结束工作正常,所以应该没有问题。

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

以下是相关的代码片段:

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

SEPARATOR是系统文件分隔符, FILEPATH是原始ODT的文件path,我将覆盖它,但是这里没有做testing。 我只需写入同一个目录下的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(); } } } 

包含要压缩的文件的目录位于用户空间中的某个位置,而不是与生成的文件位于同一目录中。 我认为这可能是麻烦,但我不能真正看到如何。 另外我觉得这个问题可能是在使用相同的stream输出,但我再也看不到。 我在一些例子和教程中看到,他们使用getPath()而不是getName()但改变了这个给我一个空的zip文件。

URI类对于处理相对path很有用。

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

上面的代码会发出stringpath/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(); } } 

此代码使得不保留date,我不知道它将如何反应像符号链接的东西。 没有尝试添加目录条目,因此不包含空目录。

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

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

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

  1. 您不保存目录path,因此无法恢复。
  2. 在Windows上,您需要使用“/”作为path分隔符。 有些解压程序不喜欢\。

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

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

只要通过java.util.zip.ZipEntry的源代码。 如果它的名字以“/”字符结尾,它将把ZipEntry当作目录。 只需以“/”作为后缀。 此外,您需要删除驱动器前缀,使其相对。

检查这个例子只是压缩空目录,

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

只要你能够在ZIP文件中创build空的和非空的目录,你的目录结构是完整的。

祝你好运。

这里是另一个例子(recursion),它也可以让你包含/排除包含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(); } } } 

如果您不想打扰处理字节inputstream,缓冲区大小和其他低级细节。 你可以从你的java代码中使用Ant的Zip库(maven dependencies可以在这里find)。 现在我制作一个包含文件和目录列表的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"); } 

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

更换,

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

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

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

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

这段代码被剪切了。 没有第三方库需要。

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