63

現在、warファイルの内容を抽出し、ディレクトリ構造にいくつかの新しいファイルを追加してから、新しいwarファイルを作成しています。

これはすべてJavaからプログラムで実行されますが、warファイルをコピーしてからファイルを追加する方が効率的ではないかと思います。そうすれば、warが拡大するまで待つ必要はなく、その後、再び圧縮されます。

ドキュメントやオンラインの例では、これを行う方法を見つけることができないようです。

誰でもいくつかのヒントや指針を与えることができますか?

アップデート:

回答の1つで言及されているTrueZipは、zipファイルに追加するのに非常に優れたJavaライブラリのようです(これを行うことは不可能であると言う他の回答にも関わらず)。

TrueZipに関する経験やフィードバックがある人、または他の同様のライブラリを推奨できる人はいますか?

4

13 に答える 13

50

他の人が述べたように、コンテンツを既存の zip (または war) に追加することはできません。ただし、抽出されたコンテンツを一時的にディスクに書き込むことなく、その場で新しい zip を作成することは可能です。これがどれほど高速になるかを推測するのは難しいですが、(少なくとも私の知る限りでは) 標準の Java で実現できる最速のものです。Carlos Tasada が述べたように、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();
    }
}
于 2010-02-15T10:17:41.530 に答える
27

以前も同様の要件がありましたが、それは zip アーカイブの読み取りと書き込みのためでした (.war 形式も同様である必要があります)。既存の Java Zip ストリームを使用して実行しようとしましたが、特にディレクトリが関係する場合は、書き込み部分が面倒であることがわかりました。

通常のファイル システムのように読み書きできる仮想ファイル システムとしてアーカイブを公開するTrueZIP (オープン ソース - Apache スタイル ライセンス) ライブラリを試すことをお勧めします。それは私にとって魅力のように機能し、開発を大幅に簡素化しました。

于 2010-02-12T12:13:45.177 に答える
14

私が書いたこのコードを使用できます

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();
    }
}
于 2012-01-12T02:04:11.680 に答える
3

あなたが説明したことを行うJavaライブラリを知りません。しかし、あなたが説明したことは実用的です。DotNetZipを使用して、.NET で実行できます。

Michael Krauklis は、war ファイルまたは zip ファイルに単純にデータを「追加」することはできないという点で正しいですが、厳密に言えば、war ファイルに「ファイルの終わり」の指示があるためではありません。これは、war (zip) 形式にディレクトリが含まれているためです。このディレクトリは通常、ファイルの末尾にあり、war ファイル内のさまざまなエントリのメタデータが含まれています。単純に war ファイルに追加すると、ディレクトリが更新されないため、ジャンクが追加された war ファイルが作成されます。

必要なのは、フォーマットを理解し、必要に応じてディレクトリを含む war ファイルまたは zip ファイルを読み取り、更新できるインテリジェントなクラスです。DotNetZip は、変更されていないエントリを解凍/再圧縮せずに、説明または希望どおりにこれを行います。

于 2010-02-10T16:06:01.050 に答える
2

Cheesoが言うように、それを行う方法はありません。AFAIKは、zipフロントエンドが内部でまったく同じように動作していることを確認してください。

とにかく、すべてを抽出/圧縮する速度が心配な場合は、SevenZipJBindingsライブラリを試してみてください。

このライブラリについては、数か月前にブログで取り上げました(自動プロモーションでごめんなさい)。一例として、java.util.zipを使用して104MBのzipファイルを抽出するには12秒かかりましたが、このライブラリを使用すると4秒かかりました。

両方のリンクで、それを使用する方法についての例を見つけることができます。

それが役に立てば幸い。

于 2010-02-12T11:41:10.143 に答える
1

さらに別の解決策:以下のコードは、他の状況でも役立つ場合があります。私はこの方法で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);
    }
}
于 2011-03-28T19:33:51.350 に答える
1

このバグレポートを参照してください。

zipファイルやtarファイルなどのあらゆる種類の構造化データで追加モードを使用することは、実際に機能することを期待できるものではありません。これらのファイル形式には、データ形式に組み込まれた固有の「ファイルの終わり」表示があります。

本当に警告解除/再警告の中間ステップをスキップしたい場合は、warファイルファイルを読み取り、すべてのzipエントリを取得してから、追加したい新しいエントリを「追加」する新しいwarファイルに書き込むことができます。完璧ではありませんが、少なくともより自動化されたソリューションです。

于 2010-02-08T17:19:20.280 に答える
1

TrueVFSを使用して既存の zip にファイルを簡単に追加できる例を次に示します。

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

TrueZIP の後継である TrueVFS は、必要に応じて内部で Java 7 NIO 2 機能を使用しますが、スレッドセーフな非同期並列圧縮などのより多くの機能を提供します。

Java 7 ZipFileSystem は、デフォルトで巨大な入力のOutOfMemoryError に対して脆弱であることにも注意してください。

于 2016-09-12T19:16:54.877 に答える