Добавление файлов в zip-файл с помощью Java - PullRequest
56 голосов
/ 08 февраля 2010

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

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

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

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

UPDATE:

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

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

Ответы [ 12 ]

78 голосов
/ 06 июля 2013

В 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");
    }
}
43 голосов
/ 15 февраля 2010

Как уже упоминалось, невозможно добавить контент в существующий 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();
    }
}
25 голосов
/ 12 февраля 2010

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

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

14 голосов
/ 12 января 2012

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

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();
    }
}
2 голосов
/ 12 февраля 2010

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

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

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

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

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

2 голосов
/ 10 февраля 2010

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

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

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

1 голос
/ 11 сентября 2014

это простой код для получения ответа с использованием сервлета и отправки ответа

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 голос
/ 28 марта 2011

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

См. отчет об ошибке .

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

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

0 голосов
/ 12 сентября 2016

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

...