Вопрос

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

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

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

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

Обновить:

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

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

Это было полезно?

Решение

В 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-архив (или войну).Однако можно создать новый zip-архив «на лету», не записывая временно извлеченный контент на диск.Трудно предположить, насколько это будет быстрее, но это самое быстрое, что вы можете получить (по крайней мере, насколько мне известно) со стандартной Java.Как упомянул Карлос Тасада, SevenZipJBindings может выжать у вас несколько дополнительных секунд, но портирование этого подхода на SevenZipJBindings все равно будет быстрее, чем использование временных файлов с той же библиотекой.

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

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

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

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

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, используя ДотНетЗип.

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

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

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

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

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

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

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

Видишь это отчет об ошибке.

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

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

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

Вывод записывается в новый 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 на огромных затратах.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top