Compression de données avec GZIP et Zip en Java

Bien que de nombreux algorithmes de compression existent, les formats Zip et GZIP restent parmi les plus courants. Cela signifie que nous disposons facilement de nobmreux outils pour manipuler nos données compressées.

Compression basique avec GZIP

L'interface GZIP est très simple, ce qui en fait un bon choix pour la compression d'un flux de données unique (plutôt qu'une collection de fichiers distincts). Voici un exemple de compression d'un fichier unique :

// {Args: GZIPcompress.java}
package com.java.io;

import java.io.BufferedReader;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class GZIPcompress {
    public static void main(String[] args) throws IOException {
        if (args.length == 0) {
            System.out.println(
                "Utilisation : \nGZIPcompress fichier\n" +
                "\tCompresse le fichier avec GZIP vers test.gz");
            System.exit(1);
        }
        try (BufferedReader source = new BufferedReader(new FileReader(args[0]));
             BufferedOutputStream sortieCompressee = new BufferedOutputStream(
                 new GZIPOutputStream(
                     new FileOutputStream("test.gz")))) {

            System.out.println("Écriture du fichier");
            int caractere;
            while ((caractere = source.read()) != -1) {
                sortieCompressee.write(caractere);
            }
        }

        System.out.println("Lecture du fichier");
        try (BufferedReader entreeDecompressee = new BufferedReader(
                new InputStreamReader(
                    new GZIPInputStream(
                        new FileInputStream("test.gz"))))) {
            String ligne;
            while ((ligne = entreeDecompressee.readLine()) != null) {
                System.out.println(ligne);
            }
        }
    }
}

L'utilisation des classes de compression est intuitive : on enveloppe simplement le flux de sortie avec GZIPOutputStream ou ZipOutputStream, et le flux d'entrée avec GZIPInputStream ou ZipInputStream. Toutes les autres opérations sont des lectures/écritures I/O standard. Cet exemple mélange des flux orientés caractère (Reader) et orienté octet. L'entrée (source) utilise un Reader, tandis que le constructeur de GZIPOutputStream n'accepte qu'un OutputStream, pas un Writer. Lors de l'ouverture du fichier, GZIPInputStream est converti en Reader.

Archivage multi-fichiers avec Zip

La bibliothèque Java pour le format Zip est plus complète. Elle permet de sauvegarder facilement plusieurs fichiers et inclut même une classe dédiée pour faciliter la lecture des fichiers Zip. Cette bibliothèque utilise le format Zip standard, garantissant une bonne compatibilité avec les outils de compression courants disponibles en ligne. L'exemple suivant gère un nombre arbitraire de fichiers passés en argument de ligne de commande. Il illustre également l'utilisation d'une classe Checksum pour calculer et vérifier la somme de contrôle des fichiers. Il existe deux types de somme de contrôle : Adler32 (plus rapide) et CRC32 (plus lent, mais plus précis).

// Utilise la compression Zip pour compresser n'importe
// quel nombre de fichiers donnés en argument.
// {Args: ZipCompress.java}
package com.java.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class ZipCompress {
    public static void main(String[] args) throws IOException {
        FileOutputStream destination = new FileOutputStream("test.zip");
        CheckedOutputStream fluxAvecSommeDeControle = new CheckedOutputStream(destination, new Adler32());
        ZipOutputStream fluxSortieZip = new ZipOutputStream(fluxAvecSommeDeControle);
        BufferedOutputStream sortieTamponnee = new BufferedOutputStream(fluxSortieZip);
        fluxSortieZip.setComment("Un test de compression Zip en Java");

        for (String fichierSource : args) {
            System.out.println("Écriture du fichier " + fichierSource);
            try (BufferedReader lecteurFichier = new BufferedReader(new FileReader(fichierSource))) {
                fluxSortieZip.putNextEntry(new ZipEntry(fichierSource));
                int octet;
                while ((octet = lecteurFichier.read()) != -1) {
                    sortieTamponnee.write(octet);
                }
                sortieTamponnee.flush();
            }
        }
        sortieTamponnee.close(); // Doit être fermé pour calculer la somme de contrôle.
        System.out.println("Somme de contrôle : " + fluxAvecSommeDeControle.getChecksum().getValue());

        // Extraction des fichiers
        System.out.println("Lecture du fichier");
        FileInputStream sourceZip = new FileInputStream("test.zip");
        CheckedInputStream fluxEntreeAvecControle = new CheckedInputStream(sourceZip, new Adler32());
        ZipInputStream fluxEntreeZip = new ZipInputStream(fluxEntreeAvecControle);
        BufferedInputStream entreeTamponnee = new BufferedInputStream(fluxEntreeZip);
        ZipEntry entreeCourante;
        while ((entreeCourante = fluxEntreeZip.getNextEntry()) != null) {
            System.out.println("Lecture du fichier " + entreeCourante.getName());
            int donnee;
            while ((donnee = entreeTamponnee.read()) != -1) {
                System.out.write(donnee);
            }
        }
        entreeTamponnee.close();
        if (args.length == 1) {
            System.out.println("Somme de contrôle : " + fluxEntreeAvecControle.getChecksum().getValue());
        }

        // Méthode alternative pour lire un fichier Zip
        try (ZipFile archiveZip = new ZipFile("test.zip")) {
            Enumeration<? extends ZipEntry> enumerEntrees = archiveZip.entries();
            while (enumerEntrees.hasMoreElements()) {
                ZipEntry entree = enumerEntrees.nextElement();
                System.out.println("Fichier : " + entree.getName());
                // ... extraction des données comme précédemment
            }
        }
    }
}

Pour chaque fichier à ajouter à l'archive, il faut appeler putNextEntry() en lui passant un objet ZipEntry. Cet objet possède une interface étendue permettant d'accéder et de définir toutes les métadonnées disponibles pour une entrée spécifique dans le fichier Zip : nom, taille compressée et non comprsesée, date, somme de contrôle CRC, données de champ supplémentaire, commentaire, méthode de compression, et si c'est une entrée de répertoire, etc. Cependant, alors que le format Zip permet de définir un mot de passe, la bibliothèque Java ne le supporte pas. Bien que CheckedInputStream et CheckedOutputStream supportent les algorithmes Adler32 et CRC32, la classe ZipEntry ne fournit une interface que pour CRC. C'est une limitation du format Zip bas niveau qui empêche d'utiliser le plus rapide Adler32.

Pour décompresser un fichier, ZipInputStream fournit une méthode getNextEntry() qui retourne la prochaine ZipEntry (s'il en existe). Une approche plus simple pour lire le contenu consiste à utiliser un objet ZipFile. Celui-ci possède une méthode entries() qui retourne une Enumeration de ZipEntry.

Pour accéder à la somme de contrôle, il faut détenir une référence à l'objet Checksum associé. Ici, nous conservons des références vers les objets CheckedOutputStream et CheckedInputStream, mais on pourrait aussi ne garder qu'une référence vers l'objet Checksum lui-même.

La méthode setComment() des flux Zip est quelque peu déroutante. Comme montré dans ZipCompress.java, on peut écrire un commentaire lors de l'écriture, mais il n'y a aucun moyen de récupérer le commentaire à partir d'un ZipInputStream. La récupération complète des commentaires semble n'être supportée que de manière itérative via ZipEntry.

L'utilisation des bibliothèques GZIP ou Zip ne se limite pas aux fichiers ; elles peuvent compresser n'importe quel type de données, y compris des données devant être transmises sur un réseau.

Étiquettes: gzip zip Java I/O Stream Checksum

Publié le 10 juin à 17h12