65 votes

Ajouter des fichiers à un fichier zip avec Java

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

Tout cela se fait par programme à partir de Java, mais je me demande s'il ne serait pas plus efficace de copier le fichier de guerre et de simplement ajouter les fichiers. Ainsi, je n'aurais pas à attendre aussi longtemps que le fichier de guerre s'étend et doit être compressé à nouveau.

Je ne parviens pas à trouver un moyen de le faire dans la documentation ou dans les exemples en ligne.

Quelqu'un peut-il donner des conseils ou des indications ?

UPDATE :

TrueZip, comme mentionné dans une des réponses, semble être une très bonne bibliothèque java pour ajouter à un fichier zip (malgré d'autres réponses qui disent qu'il n'est pas possible de le faire).

Quelqu'un a-t-il une expérience ou un retour sur TrueZip ou peut-il recommander d'autres librairies similaires ?

2 votes

J'ai trouvé ce message dans la liste de diffusion de truezip : truezip.dev.java.net/servlets/ conclusion : truezip actuellement ne prend pas en charge opérations d'ajout rapides

100voto

Grzegorz Żur Points 7263

En Java 7, nous avons Système de fichiers Zip qui permet d'ajouter et de modifier des fichiers dans un zip (jar, war) sans repackaging manuel.

Nous pouvons écrire directement sur les fichiers à l'intérieur des 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");
    }
}

1 votes

Comment pouvons-nous utiliser celui-ci en utilisant smb ? Je veux ajouter des fichiers à un fichier zip qui se trouve dans une machine Windows depuis une machine osx/linux.

0 votes

@NirmalRaghavan Ceci est hors du champ de cette question. Pour SMB/CIFS, voir comment monter un lecteur réseau Windows sous Linux.

0 votes

Merci pour l'exemple. Il s'avère que j'ai été trop stupide pour utiliser ZIP-FileSystems jusqu'à maintenant.

52voto

sfussenegger Points 16204

Comme d'autres l'ont mentionné, il n'est pas possible d'ajouter du contenu à un zip (ou war) existant. Cependant, il est possible de créer un nouveau zip à la volée sans écrire temporairement le contenu extrait sur le disque. Il est difficile d'évaluer la rapidité de cette méthode, mais c'est la plus rapide que l'on puisse obtenir (du moins pour autant que je sache) avec Java standard. Comme mentionné par Carlos Tasada, SevenZipJBindings pourrait vous faire gagner quelques secondes supplémentaires, mais le portage de cette approche sur 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 zip existant (war.zip) et ajoute un fichier supplémentaire (answer.txt) à un nouveau zip (append.zip). Il suffit de disposer de Java 5 ou d'une version plus récente, sans bibliothèques supplémentaires.

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

0 votes

Mon fichier de guerre est compressé à 30 Mo. Je ne suis pas sûr que cette approche soit la meilleure car elle nécessitera beaucoup de mémoire. Je mets déjà en cache beaucoup de requêtes de base de données en mémoire et cela pourrait rendre l'empreinte mémoire trop importante.

3 votes

@Grouchal En réalité, vous n'aurez jamais besoin de plus de mémoire que BUFFER (J'ai choisi 4MB, mais vous êtes libre de l'adapter à vos besoins - cela ne devrait pas faire de mal de le réduire à quelques Ko seulement). Le fichier n'est jamais stocké entièrement en mémoire.

0 votes

L'idée est de décompresser le contenu de la guerre existante en BUFFER et le comprimer dans une nouvelle archive - entrée après entrée. Après cela, vous vous retrouvez avec la même archive qui est prête à recevoir d'autres entrées. J'ai choisi d'écrire "42" dans answer.txt. C'est là que vous devez placer votre code pour ajouter d'autres entrées.

28voto

gnlogic Points 640

J'ai eu un besoin similaire il y a quelques temps - 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, surtout lorsqu'il s'agissait de répertoires.

Je vous recommande d'essayer le TrueZIP (open source - licence style apache) qui expose n'importe quelle archive comme un 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.

0 votes

Cela semble très bien - j'aimerais savoir s'il y a des problèmes de performance à connaître ?

0 votes

Jusqu'à présent, j'ai pu l'utiliser efficacement avec des fichiers de taille modérée (3 Mo, etc.). Je n'ai pas rencontré de problèmes de performances.

6 votes

Il y a une nouvelle option dans Java 7, une ZipFileSystem

14voto

Liam Points 198

Vous pouvez utiliser ce bout 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();
    }
}

0 votes

Et avec ce code, les nouveaux fichiers ont la priorité sur les anciens.

0 votes

Vous pouvez également modifier la taille de la mémoire tampon en fonction de vos besoins, celle qui est dans le code pour l'instant ne convient qu'aux petits fichiers

0 votes

J'ai vraiment aimé ce code mais j'avais besoin d'autre chose où j'avais besoin d'ajouter des fichiers dans les dossiers du zip et pas seulement dans la racine du zip j'ai posté ma méthode modifiée ici stackoverflow.com/questions/9300115/ J'espère que cela aidera d'autres personnes. Merci beaucoup, Liam, pour ce super code de base.

3voto

Cheeso Points 87022

Je ne connais pas de bibliothèque Java qui fasse ce que vous décrivez. Mais ce que vous décrivez 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 de guerre ou à un fichier zip, mais ce n'est pas parce qu'il y a une indication de "fin de fichier", à proprement parler, dans un fichier de guerre. C'est parce que le format war (zip) comprend un répertoire, qui est normalement présent à la fin du fichier, et qui contient des métadonnées pour les différentes entrées du fichier war. L'ajout naïf à un fichier de guerre n'entraîne aucune mise à jour du répertoire, et vous obtenez donc un fichier de guerre auquel sont ajoutés des éléments inutiles.

Ce qui est nécessaire, c'est une classe intelligente qui comprend le format, et peut lire+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, exactement comme vous l'avez décrit ou souhaité.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X