web-dev-qa-db-fra.com

Ajout de fichiers à un fichier Zip avec Java

Je suis en train d'extraire le contenu d'un fichier war, puis d'ajouter de nouveaux fichiers à la structure de répertoires, puis de créer un nouveau fichier war.

Tout cela se fait par programmation à partir de Java - mais je me demande s'il ne serait pas plus efficace de copier le fichier war et de simplement en ajouter les fichiers - alors je n'aurais pas à attendre tant que la guerre s'agrandira, puis être compressé à nouveau.

Je n'arrive pas à trouver un moyen de faire cela dans la documentation ni dans les exemples en ligne.

Tout le monde peut donner des conseils ou des indications?

METTRE À JOUR:

TrueZip comme mentionné dans l’une des réponses semble être une très bonne bibliothèque Java à ajouter à un fichier Zip (malgré d’autres réponses indiquant qu’il est impossible de le faire).

Quelqu'un a de l'expérience ou des commentaires sur TrueZip ou peut recommander d'autres bibliothèques similaires?

52
Grouchal

En Java 7, nous avons Zip File System qui permet d’ajouter et de modifier des fichiers dans Zip (jar, war) sans reconditionnement manuel.

Nous pouvons écrire directement dans des fichiers au sein de fichiers Zip, comme dans l'exemple suivant.

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

Comme d'autres l'ont mentionné, il n'est pas possible d'ajouter du contenu à un fichier Zip (ou à une guerre) existant. Cependant, il est possible de créer un nouveau fichier Zip à la volée sans écrire temporairement le contenu extrait sur le disque. Il est difficile de deviner à quel point cela sera plus rapide, mais c'est le plus rapide que vous puissiez obtenir (du moins autant que je sache) avec Java standard. Comme l'a mentionné Carlos Tasada, SevenZipJBindings peut vous faire perdre quelques secondes supplémentaires, mais le portage de cette approche vers SevenZipJBindings sera toujours plus rapide que l'utilisation de fichiers temporaires avec la même bibliothèque.

Voici un code qui écrit le contenu d'un fichier Zip existant (war.Zip) et ajoute un fichier supplémentaire (answer.txt) à un nouveau fichier Zip (append.Zip). Tout ce qu'il faut, c'est Java 5 ou une version ultérieure, aucune bibliothèque supplémentaire n'est nécessaire.

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

J'avais une exigence similaire à un moment donné - mais c'était pour lire et écrire des archives Zip (le format .war devrait être similaire). J'ai essayé de le faire avec les flux Java Zip existants, mais j'ai trouvé la partie écriture fastidieuse, en particulier lorsque des répertoires étaient impliqués. 

Je vous recommande d'essayer la bibliothèque TrueZIP (open source - licence sous Apache) qui expose toutes les archives en tant que système de fichiers virtuel dans lequel vous pouvez lire et écrire comme un système de fichiers normal. Cela a fonctionné comme un charme pour moi et a grandement simplifié mon développement. 

24
gnlogic

Vous pouvez utiliser ce morceau de code que j'ai écrit

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

Je ne connais pas de bibliothèque Java qui fasse ce que vous décrivez. Mais ce que vous avez décrit est pratique. Vous pouvez le faire en .NET, en utilisant DotNetZip

Michael Krauklis a raison de dire que vous ne pouvez pas simplement "ajouter" des données à un fichier war ou un fichier Zip, mais ce n'est pas parce qu'il y a une indication "fin de fichier", à proprement parler, dans un fichier war. C'est parce que le format war (Zip) comprend un répertoire, normalement présent à la fin du fichier, contenant des métadonnées pour les différentes entrées du fichier war. L'ajout naïf à un fichier war n'entraîne aucune mise à jour du répertoire. Vous avez donc juste un fichier war avec des fichiers indésirables ajouté à celui-ci. 

Ce qui est nécessaire, c'est une classe intelligente qui comprend le format et peut lire et mettre à jour un fichier war ou un fichier Zip, y compris le répertoire, le cas échéant. DotNetZip fait cela sans décompresser/recompresser les entrées inchangées, comme vous le décrivez ou le souhaitez. 

2
Cheeso

Comme le dit Cheeso, il n’ya aucun moyen de le faire. Autant que je sache, les frontaux Zip fonctionnent exactement de la même manière que vous le faites en interne.

Quoi qu'il en soit, si vous êtes inquiet à propos de la vitesse d'extraction/compression de tout, vous pouvez essayer la bibliothèque SevenZipJBindings .

J'ai couvert cette bibliothèque dans mon blog il y a quelques mois (désolé pour l'auto-promotion). À titre d'exemple, l'extraction d'un fichier Zip de 104 Mo à l'aide de Java.util.Zip m'a pris 12 secondes, tandis que l'utilisation de cette bibliothèque prenait 4 secondes.

Vous trouverez dans les deux liens des exemples d'utilisation.

J'espère que ça aide.

2
Carlos Tasada

cela un code simple pour obtenir une réponse à l'aide de servlet et envoyer une réponse

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

Encore une autre solution: Vous trouverez peut-être que le code ci-dessous est utile dans d’autres situations. J'ai utilisé ce moyen pour compiler des répertoires Java, générer des fichiers JAR, mettre à jour des fichiers 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

Voir ce rapport de bogue .

Utiliser le mode append sur n’importe quel type de données structurées telles que les fichiers Zip ou tar les fichiers n’est pas quelque chose que vous pouvez vraiment s'attendre à travailler. Ces formats de fichiers avoir une "fin de fichier" intrinsèque indication intégrée dans le format de données.

Si vous voulez vraiment ignorer l'étape intermédiaire de révocation/relecture, vous pouvez lire le fichier de fichier de guerre, récupérer toutes les entrées Zip, puis écrire dans un nouveau fichier de guerre "en ajoutant" les nouvelles entrées que vous souhaitez ajouter. Pas parfait, mais au moins une solution plus automatisée.

1
Michael Krauklis

cela fonctionne à 100%, si vous ne voulez pas utiliser de bibliothèques supplémentaires .. 1) d’abord, la classe qui ajoute des fichiers au fichier 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) vous pouvez l'appeler dans votre contrôleur ..

//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

Voici la version Java 1.7 de Liam answer qui utilise try avec des ressources et Apache Commons IO.

La sortie est écrite dans un nouveau fichier Zip, mais elle peut être facilement modifiée pour écrire dans le fichier d'origine. 

  /**
   * 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

Voici des exemples de la facilité avec laquelle des fichiers peuvent être ajoutés à un fichier Zip existant en utilisant 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, le successeur de TrueZIP, utilise les fonctionnalités de Java 7 NIO 2 sous le capot, le cas échéant, mais offre beaucoup plus de fonctionnalités comme la compression parallèle asynchrone thread-safe.

Notez également que Java 7 ZipFileSystem est par défaut vulnérable à OutOfMemoryError sur des entrées volumineuses.

0
Vadzim