使用 Java 将文件附加到 zip 文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2223434/
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-13 04:54:20  来源:igfitidea点击:

Appending files to a zip file with Java

javazipwartruezip

提问by Grouchal

I am currently extracting the contents of a war file and then adding some new files to the directory structure and then creating a new war file.

我目前正在提取一个war文件的内容,然后将一些新文件添加到目录结构中,然后创建一个新的war文件。

This is all done programatically from Java - but I am wondering if it wouldn't be more efficient to copy the war file and then just append the files - then I wouldn't have to wait so long as the war expands and then has to be compressed again.

这一切都是从 Java 以编程方式完成的 - 但我想知道复制战争文件然后只是附加文件是否会更有效 - 那么只要战争扩大然后我就不必等待再次压缩。

I can't seem to find a way to do this in the documentation though or any online examples.

我似乎无法在文档或任何在线示例中找到一种方法来做到这一点。

Anyone can give some tips or pointers?

任何人都可以提供一些提示或指示?

UPDATE:

更新:

TrueZip as mentioned in one of the answers seems to be a very good java library to append to a zip file (despite other answers that say it is not possible to do this).

其中一个答案中提到的 TrueZip 似乎是一个非常好的 Java 库,可以附加到 zip 文件(尽管其他答案说不可能这样做)。

Anyone have experience or feedback on TrueZip or can recommend other similar libaries?

任何人都对 TrueZip 有经验或反馈,或者可以推荐其他类似的库?

采纳答案by Grzegorz ?ur

In Java 7 we got Zip File Systemthat allows adding and changing files in zip (jar, war) without manual repackaging.

在 Java 7 中,我们获得了Zip 文件系统,它允许在 zip(jar、war)中添加和更改文件,而无需手动重新打包。

We can directly write to files inside zip files as in the following example.

我们可以直接写入 zip 文件中的文件,如下例所示。

Map<String, String> env = new HashMap<>(); 
env.put("create", "true");
Path path = Paths.get("test.zip");
URI uri = URI.create("jar:" + path.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, env))
{
    Path nf = fs.getPath("new.txt");
    try (Writer writer = Files.newBufferedWriter(nf, StandardCharsets.UTF_8, StandardOpenOption.CREATE)) {
        writer.write("hello");
    }
}

回答by Michael Krauklis

See this bug report.

请参阅此错误报告

Using append mode on any kind of structured data like zip files or tar files is not something you can really expect to work. These file formats have an intrinsic "end of file" indication built into the data format.

对任何类型的结构化数据(如 zip 文件或 tar 文件)使用附加模式并不是您真正期望的工作。这些文件格式具有内置于数据格式中的内在“文件结束”指示。

If you really want to skip the intermediate step of un-waring/re-waring, you could read the war file file, get all the zip entries, then write to a new war file "appending" the new entries you wanted to add. Not perfect, but at least a more automated solution.

如果您真的想跳过取消警告/重新警告的中间步骤,您可以读取战争文件文件,获取所有 zip 条目,然后写入一个新的战争文件,“附加”您要添加的新条目。不完美,但至少是一个更自动化的解决方案。

回答by Cheeso

I don't know of a Java library that does what you describe. But what you described is practical. You can do it in .NET, using DotNetZip.

我不知道可以执行您所描述的 Java 库。但是你描述的很实用。您可以在 .NET 中使用DotNetZip完成

Michael Krauklis is correct that you cannot simply "append" data to a war file or zip file, but it is not because there is an "end of file" indication, strictly speaking, in a war file. It is because the war (zip) format includes a directory, which is normally present at the end of the file, that contains metadata for the various entries in the war file. Naively appending to a war file results in no update to the directory, and so you just have a war file with junk appended to it.

Michael Krauklis 是正确的,您不能简单地将数据“附加”到 war 文件或 zip 文件,但这并不是因为严格来说,war 文件中有“文件结尾”指示。这是因为 war (zip) 格式包含一个目录,该目录通常位于文件末尾,其中包含 war 文件中各个条目的元数据。天真地附加到一个war 文件不会更新目录,所以你只有一个附加了垃圾的war 文件。

What's necessary is an intelligent class that understands the format, and can read+update a war file or zip file, including the directory as appropriate. DotNetZip does this, without uncompressing/recompressing the unchanged entries, just as you described or desired.

需要的是一个理解格式的智能类,并且可以读取+更新war文件或zip文件,包括适当的目录。DotNetZip 执行此操作,无需解压缩/重新压缩未更改的条目,正如您所描述或希望的那样。

回答by Carlos Tasada

As Cheeso says, there's no way of doing it. AFAIK the zip front-ends are doing exactly the same as you internally.

正如 Cheeso 所说,没有办法做到这一点。AFAIK zip 前端在内部所做的与您完全相同。

Anyway if you're worried about the speed of extracting/compressing everything, you may want to try the SevenZipJBindingslibrary.

无论如何,如果您担心提取/压缩所有内容的速度,您可能想尝试SevenZipJBindings库。

I covered this library in my blogsome months ago (sorry for the auto-promotion). Just as an example, extracting a 104MB zip file using the java.util.zip took me 12 seconds, while using this library took 4 seconds.

几个月前我在我的博客中介绍了这个库(抱歉自动推广)。举个例子,使用 java.util.zip 提取一个 104MB 的 zip 文件需要 12 秒,而使用这个库需要 4 秒。

In both links you can find examples about how to use it.

在这两个链接中,您都可以找到有关如何使用它的示例。

Hope it helps.

希望能帮助到你。

回答by gnlogic

I had a similar requirement sometime back - but it was for reading and writing zip archives (.war format should be similar). I tried doing it with the existing Java Zip streams but found the writing part cumbersome - especially when directories where involved.

我曾经有过类似的要求 - 但它用于读取和写入 zip 档案(.war 格式应该类似)。我尝试使用现有的 Java Zip 流执行此操作,但发现编写部分很麻烦——尤其是在涉及目录时。

I'll recommend you to try out the TrueZIP(open source - apache style licensed) library that exposes any archive as a virtual file system into which you can read and write like a normal filesystem. It worked like a charm for me and greatly simplified my development.

我会建议您尝试TrueZIP(开源 - Apache 风格许可)库,该库将任何存档公开为虚拟文件系统,您可以像普通文件系统一样在其中读写。它对我来说就像一种魅力,大大简化了我的开发。

回答by sfussenegger

As others mentioned, it's not possible to append content to an existing zip (or war). However, it's possible to create a new zip on the fly without temporarily writing extracted content to disk. It's hard to guess how much faster this will be, but it's the fastest you can get (at least as far as I know) with standard Java. As mentioned by Carlos Tasada, SevenZipJBindings might squeeze out you some extra seconds, but porting this approach to SevenZipJBindings will still be faster than using temporary files with the same library.

正如其他人提到的,不可能将内容附加到现有的 zip(或 war)。但是,可以在不临时将提取的内容写入磁盘的情况下动态创建新的 zip。很难猜测这会快多少,但这是使用标准 Java 可以获得的最快速度(至少据我所知)。正如 Carlos Tasada 所提到的,SevenZipJBindings 可能会让您多花几秒钟的时间,但是将这种方法移植到 SevenZipJBindings 仍然比使用具有相同库的临时文件要快。

Here's some code that writes the contents of an existing zip (war.zip) and appends an extra file (answer.txt) to a new zip (append.zip). All it takes is Java 5 or later, no extra libraries needed.

下面是一些写入现有 zip (war.zip) 内容并将额外文件 (answer.txt) 附加到新 zip (append.zip) 的代码。只需要 Java 5 或更高版本,不需要额外的库。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class Main {

    // 4MB buffer
    private static final byte[] BUFFER = new byte[4096 * 1024];

    /**
     * copy input to output stream - available in several StreamUtils or Streams classes 
     */    
    public static void copy(InputStream input, OutputStream output) throws IOException {
        int bytesRead;
        while ((bytesRead = input.read(BUFFER))!= -1) {
            output.write(BUFFER, 0, bytesRead);
        }
    }

    public static void main(String[] args) throws Exception {
        // read war.zip and write to append.zip
        ZipFile war = new ZipFile("war.zip");
        ZipOutputStream append = new ZipOutputStream(new FileOutputStream("append.zip"));

        // first, copy contents from existing war
        Enumeration<? extends ZipEntry> entries = war.entries();
        while (entries.hasMoreElements()) {
            ZipEntry e = entries.nextElement();
            System.out.println("copy: " + e.getName());
            append.putNextEntry(e);
            if (!e.isDirectory()) {
                copy(war.getInputStream(e), append);
            }
            append.closeEntry();
        }

        // now append some extra content
        ZipEntry e = new ZipEntry("answer.txt");
        System.out.println("append: " + e.getName());
        append.putNextEntry(e);
        append.write("42\n".getBytes());
        append.closeEntry();

        // close
        war.close();
        append.close();
    }
}

回答by Barmak

Yet Another Solution: You may find code below useful in other situations as well. I have used ant this way to compile Java directories, generating jar files, updating zip files,...

另一种解决方案:您可能会发现以下代码在其他情况下也很有用。我用 ant 这种方式来编译 Java 目录,生成 jar 文件,更新 zip 文件,...

    public static void antUpdateZip(String zipFilePath, String libsToAddDir) {
    Project p = new Project();
    p.init();

    Target target = new Target();
    target.setName("zip");
    Zip task = new Zip();
    task.init();
    task.setDestFile(new File(zipFilePath));
    ZipFileSet zipFileSet = new ZipFileSet();
    zipFileSet.setPrefix("WEB-INF/lib");
    zipFileSet.setDir(new File(libsToAddDir));
    task.addFileset(zipFileSet);
    task.setUpdate(true);

    task.setProject(p);
    task.init();
    target.addTask(task);
    target.setProject(p);
    p.addTarget(target);

    DefaultLogger consoleLogger = new DefaultLogger();
    consoleLogger.setErrorPrintStream(System.err);
    consoleLogger.setOutputPrintStream(System.out);
    consoleLogger.setMessageOutputLevel(Project.MSG_DEBUG);
    p.addBuildListener(consoleLogger);

    try {
        // p.fireBuildStarted();

        // ProjectHelper helper = ProjectHelper.getProjectHelper();
        // p.addReference("ant.projectHelper", helper);
        // helper.parse(p, buildFile);
        p.executeTarget(target.getName());
        // p.fireBuildFinished(null);
    } catch (BuildException e) {
        p.fireBuildFinished(e);
        throw new AssertionError(e);
    }
}

回答by Liam Haworth

You could use this bit of code I wrote

你可以使用我写的这段代码

public static void addFilesToZip(File source, File[] files)
{
    try
    {

        File tmpZip = File.createTempFile(source.getName(), null);
        tmpZip.delete();
        if(!source.renameTo(tmpZip))
        {
            throw new Exception("Could not make temp file (" + source.getName() + ")");
        }
        byte[] buffer = new byte[1024];
        ZipInputStream zin = new ZipInputStream(new FileInputStream(tmpZip));
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(source));

        for(int i = 0; i < files.length; i++)
        {
            InputStream in = new FileInputStream(files[i]);
            out.putNextEntry(new ZipEntry(files[i].getName()));
            for(int read = in.read(buffer); read > -1; read = in.read(buffer))
            {
                out.write(buffer, 0, read);
            }
            out.closeEntry();
            in.close();
        }

        for(ZipEntry ze = zin.getNextEntry(); ze != null; ze = zin.getNextEntry())
        {
            out.putNextEntry(ze);
            for(int read = zin.read(buffer); read > -1; read = zin.read(buffer))
            {
                out.write(buffer, 0, read);
            }
            out.closeEntry();
        }

        out.close();
        tmpZip.delete();
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
}

回答by peceps

Here is Java 1.7 version of Liam answer which uses try with resources and Apache Commons IO.

这是 Liam 答案的 Java 1.7 版本,它使用 try 与资源和 Apache Commons IO。

The output is written to a new zip file but it can be easily modified to write to the original file.

输出将写入新的 zip 文件,但可以轻松修改以写入原始文件。

  /**
   * Modifies, adds or deletes file(s) from a existing zip file.
   *
   * @param zipFile the original zip file
   * @param newZipFile the destination zip file
   * @param filesToAddOrOverwrite the names of the files to add or modify from the original file
   * @param filesToAddOrOverwriteInputStreams the input streams containing the content of the files
   * to add or modify from the original file
   * @param filesToDelete the names of the files to delete from the original file
   * @throws IOException if the new file could not be written
   */
  public static void modifyZipFile(File zipFile,
      File newZipFile,
      String[] filesToAddOrOverwrite,
      InputStream[] filesToAddOrOverwriteInputStreams,
      String[] filesToDelete) throws IOException {


    try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(newZipFile))) {

      // add existing ZIP entry to output stream
      try (ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry entry = null;
        while ((entry = zin.getNextEntry()) != null) {
          String name = entry.getName();

          // check if the file should be deleted
          if (filesToDelete != null) {
            boolean ignoreFile = false;
            for (String fileToDelete : filesToDelete) {
              if (name.equalsIgnoreCase(fileToDelete)) {
                ignoreFile = true;
                break;
              }
            }
            if (ignoreFile) {
              continue;
            }
          }

          // check if the file should be kept as it is
          boolean keepFileUnchanged = true;
          if (filesToAddOrOverwrite != null) {
            for (String fileToAddOrOverwrite : filesToAddOrOverwrite) {
              if (name.equalsIgnoreCase(fileToAddOrOverwrite)) {
                keepFileUnchanged = false;
              }
            }
          }

          if (keepFileUnchanged) {
            // copy the file as it is
            out.putNextEntry(new ZipEntry(name));
            IOUtils.copy(zin, out);
          }
        }
      }

      // add the modified or added files to the zip file
      if (filesToAddOrOverwrite != null) {
        for (int i = 0; i < filesToAddOrOverwrite.length; i++) {
          String fileToAddOrOverwrite = filesToAddOrOverwrite[i];
          try (InputStream in = filesToAddOrOverwriteInputStreams[i]) {
            out.putNextEntry(new ZipEntry(fileToAddOrOverwrite));
            IOUtils.copy(in, out);
            out.closeEntry();
          }
        }
      }

    }

  }

回答by erdem karayer

this a simple code to get a response with using servlet and send a response

这是一个使用 servlet 获取响应并发送响应的简单代码

myZipPath = bla bla...
    byte[] buf = new byte[8192];
    String zipName = "myZip.zip";
    String zipPath = myzippath+ File.separator+"pdf" + File.separator+ zipName;
    File pdfFile = new File("myPdf.pdf");
    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipPath));
    ZipEntry zipEntry = new ZipEntry(pdfFile.getName());
    out.putNextEntry(zipEntry);
    InputStream in = new FileInputStream(pdfFile);
    int len;
    while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
     }
    out.closeEntry();
    in.close();
     out.close();
                FileInputStream fis = new FileInputStream(zipPath);
                response.setContentType("application/zip");
                response.addHeader("content-disposition", "attachment;filename=" + zipName);
    OutputStream os = response.getOutputStream();
            int length = is.read(buffer);
            while (length != -1)
            {
                os.write(buffer, 0, length);
                length = is.read(buffer);
            }