it-roy-ru.com

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

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

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

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

Кто-нибудь может дать несколько советов или указателей?

Обновление:

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

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

52
Grouchal

В Java 7 мы получили Zip File System , которая позволяет добавлять и изменять файлы в 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");
    }
}
75
Grzegorz Żur

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

Вот некоторый код, который записывает содержимое существующего 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();
    }
}
42
sfussenegger

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

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

24
gnlogic

Вы можете использовать этот фрагмент кода, который я написал

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

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

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

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

2
Cheeso

Как говорит Cheeso, это невозможно сделать. AFAIK пользовательские интерфейсы Zip работают точно так же, как и вы.

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

Я освещал эту библиотеку в своем блоге несколько месяцев назад (извините за автоматическое продвижение). Например, извлечение файла Zip размером 104 МБ с использованием Java.util.Zip заняло у меня 12 секунд, а использование этой библиотеки - 4 секунды.

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

Надеюсь, поможет.

2
Carlos Tasada

это простой код, чтобы получить ответ с помощью сервлета и отправить ответ

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);
            }
1
erdem karayer

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

Смотрите это сообщение об ошибке .

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

Если вы действительно хотите пропустить промежуточный этап отмены/повторной обработки, вы можете прочитать файл war-файла, получить все записи Zip, а затем записать в новый war-файл, «добавляя» новые записи, которые вы хотите добавить. Не идеально, но, по крайней мере, более автоматизированное решение.

1
Michael Krauklis

это работает на 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());
0
diego matos - keke

Вот версия ответа Liam для Java 1.7, которая использует 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();
          }
        }
      }

    }

  }
0
peceps

Вот примеры того, как легко файлы могут быть добавлены к существующему 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 на огромных входах.

0
Vadzim