web-dev-qa-db-ja.com

Javaを使用してZipファイルにファイルを追加する

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

これはすべてJava-からプログラムで行われますが、warファイルをコピーしてからファイルを追加する方が効率的でないのではないかと思います。その後、待つ必要はありません。戦争が拡大し、その後再び圧縮する必要がある限り。

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

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

更新:

回答の1つで言及したTrueZipは、Zipファイルに追加するのに非常に優れたJavaライブラリです(他の回答にもかかわらず、これを行うことはできないと言っています)。

TrueZipの経験やフィードバックはありますか、他の同様のライブラリをお勧めしますか?

56
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");
    }
}
78
Grzegorz Żur

他の人が述べたように、既存のZip(または戦争)にコンテンツを追加することはできません。ただし、抽出されたコンテンツを一時的にディスクに書き込むことなく、その場で新しいZipを作成することは可能です。これがどれほど速くなるかを推測することは困難ですが、標準Javaで(少なくとも私が知る限り)取得できる最速です。 Carlos Tasadaが述べたように、SevenZipJBindingsは数秒余分に絞り出すかもしれませんが、このアプローチをSevenZipJBindingsに移植することは、同じライブラリで一時ファイルを使用するよりも高速です。

既存のZip(war.Zip)の内容を書き込み、新しいZip(append.Zip)に追加のファイル(answer.txt)を追加するコードを次に示します。必要なのは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();
    }
}
44
sfussenegger

いつか似たような要件がありました-しかし、それはZipアーカイブの読み取りと書き込み用でした(.war形式は似ているはずです)。既存のJava Zipストリームで試してみましたが、特にディレクトリが関係する場合は、書き込み部分が扱いにくいことがわかりました。

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

25
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あなたが説明したことを行うライブラリ。しかし、あなたが説明したことは実用的です。 DotNetZip を使用して.NETでそれを行うことができます。

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

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

2
Cheeso

Cheesoが言うように、それを行う方法はありません。私の知る限り、Zipフロントエンドは内部的にあなたとまったく同じことをしています。

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

このライブラリについては、数か月前に ブログ で取り上げました(自動プロモーションについてはごめんなさい)。例として、Java.util.Zipを使用して104MBの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

追加のライブラリを使用したくない場合、これは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

Java 1.7バージョンのLiamアンサーは、リソースとApache Commons IOでtryを使用します。

出力は新しい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

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に対して脆弱 が巨大な入力であることに注意してください。

0
Vadzim