Добавление файлов в zip-файл с помощью Java

в настоящее время я извлекаю содержимое файла war, а затем добавляю некоторые новые файлы в структуру каталогов, а затем создаю новый файл war.

все это делается программно с Java-но мне интересно, не было бы более эффективным скопировать файл war, а затем просто добавить файлы - тогда мне не пришлось бы ждать, пока война расширится, а затем снова должна быть сжата.

Я не могу найти способ сделать это в документации Хотя или любые онлайн-примеры.

кто-нибудь может дать некоторые подсказки или указатели?

обновление:

TrueZip, как упоминалось в одном из ответов, кажется, очень хорошая библиотека java для добавления в zip-файл (несмотря на другие ответы, которые говорят, что это невозможно сделать).

кто-нибудь имеет опыт или отзывы о TrueZip или может рекомендовать другие подобные библиотеки?

12 ответов


в Java 7 мы получили Файловая Система Zip что позволяет добавлять и изменять файлы в zip (jar, war) без ручной переупаковки.

мы можем напрямую записывать в файлы внутри 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");
    }
}

Как уже упоминалось, невозможно добавить контент к существующему zip (или war). Тем не менее, можно создать новый zip на лету без временной записи извлеченного содержимого на диск. Трудно догадаться, насколько быстрее это будет, но это самый быстрый, который вы можете получить (по крайней мере, насколько я знаю) со стандартной Java. Как упоминал Карлос Тасада, SevenZipJBindings может выжать из вас несколько дополнительных секунд, но перенос этого подхода на SevenZipJBindings все равно будет быстрее, чем использование временных файлов с той же библиотекой.

вот некоторый код, который записывает содержимое существующего zip (war.ZIP) и добавляет дополнительный файл (ответ.txt) на новый zip (append.промелькнуть.) Все, что требуется, это 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();
    }
}

У меня было аналогичное требование когда - то назад-но это было для чтения и записи zip-архивов (.формат войны должен быть похож). Я попытался сделать это с существующими потоками Java Zip, но нашел часть записи громоздкой - особенно когда каталоги, в которых участвуют.

Я рекомендую вам попробовать TrueZIP (open source - Apache style licensed) библиотека, которая предоставляет любой архив как виртуальную файловую систему, в которой вы можете читать и писать как обычно файловая система. Он работал как шарм для меня и значительно упростило мое развитие.


вы можете использовать этот кусок кода я написал

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

Я не знаю библиотеки Java, которая делает то, что вы описываете. Но то, что вы описали, практично. Вы можете сделать это в .NET, используя DotNetZip.

Майкл Крауклис прав, что вы не можете просто "добавить" данные в файл войны или zip-файл, но это не потому, что в файле войны есть указание "конец файла". Это потому, что формат war (zip) включает в себя каталог, который обычно присутствует в конце файла, который содержит метаданные для различных записей в файле war. Наивное добавление к файлу war не приводит к обновлению каталога, и поэтому у вас просто есть файл war с добавленным к нему мусором.

необходим интеллектуальный класс, который понимает формат и может читать + обновлять файл war или zip-файл, включая соответствующий каталог. DotNetZip делает это без распаковки / повторного сжатия неизмененных записей, как вы описали или пожелали.


Как говорит Чизо, нет никакого способа сделать это. AFAIK передние концы zip делают то же самое, что и вы внутри.

в любом случае, если вы беспокоитесь о скорости извлечения/сжатия всего, вы можете попробовать SevenZipJBindings библиотека.

Я покрыл эту библиотеку в моем блог несколько месяцев назад (извините за авто-продвижение). Как пример, извлечение 104mb zip-файла с помощью java.утиль.zip занял у меня 12 секунд, при использовании этой библиотеки занимает 4 секунды.

в обеих ссылках вы можете найти примеры того, как его использовать.

надеюсь, что это помогает.


посмотреть этот сообщить об ошибке.

используя режим добавления на любом виде структурированные данные, такие как zip-файлы или tar файлы-это не то, что вы действительно можете ожидайте работы. Этот формат файла имейте внутренний " конец файла" индикация встроена в формат данных.

Если вы действительно хотите пропустить промежуточный шаг un-waring / re-waring, вы можете прочитать файл файла войны, получить все записи zip, а затем написать в новый файл войны " добавление" новые записи, которые вы хотели добавить. Не идеальное, но, по крайней мере, более автоматизированное решение.


еще одно решение: вы можете найти код ниже полезным и в других ситуациях. Я использовал 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);
    }
}

Это простой код для получения ответа с помощью сервлета и отправки ответа

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

вот Java 1.7 версия Liam answer, которая использует try с ресурсами и Apache Commons IO.

вывод записывается в новый 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();
          }
        }
      }

    }

  }

это работает 100% , если вы не хотите использовать дополнительные библиотеки .. 1) Во-первых, класс, который добавляет файлы в zip ..

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class AddZip {

    public void AddZip() {
    }

    public void addToZipFile(ZipOutputStream zos, String nombreFileAnadir, String nombreDentroZip) {
        FileInputStream fis = null;
        try {
            if (!new File(nombreFileAnadir).exists()) {//NO EXISTE 
                System.out.println(" No existe el archivo :  " + nombreFileAnadir);return;
            }
            File file = new File(nombreFileAnadir);
            System.out.println(" Generando el archivo '" + nombreFileAnadir + "' al ZIP ");
            fis = new FileInputStream(file);
            ZipEntry zipEntry = new ZipEntry(nombreDentroZip);
            zos.putNextEntry(zipEntry);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {zos.write(bytes, 0, length);}
            zos.closeEntry();
            fis.close();

        } catch (FileNotFoundException ex ) {
            Logger.getLogger(AddZip.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(AddZip.class.getName()).log(Level.SEVERE, null, ex);
        } 
    }

}

2) Вы можете вызвать его в ваш контроллер ..

//in the top
try {
fos = new FileOutputStream(rutaZip);
zos =   new ZipOutputStream(fos);
} catch (FileNotFoundException ex) {
Logger.getLogger(UtilZip.class.getName()).log(Level.SEVERE, null, ex);
}

...
//inside your method
addZip.addToZipFile(zos, pathFolderFileSystemHD() + itemFoto.getNombre(), "foto/" + itemFoto.getNombre());

вот примеры того, как легко файлы могут быть добавлены к существующему zip с помощью TrueVFS:

// append a file to archive under different name
TFile.cp(new File("existingFile.txt"), new TFile("archive.zip", "entry.txt"));

// recusively append a dir to the root of archive
TFile src = new TFile("dirPath", "dirName");
src.cp_r(new TFile("archive.zip", src.getName()));

TrueVFS, преемник TrueZIP, использует функции Java 7 NIO 2 под капотом, когда это необходимо, но предлагает гораздо больше возможностей как потокобезопасное асинхронное параллельное сжатие.

остерегайтесь также, что Java 7 ZipFileSystem по умолчанию уязвимы для OutOfMemoryError на огромные вложения.