Question

Lorsque je crée une archive zip avec java.util.zip. * , existe-t-il un moyen de fractionner l'archive obtenue en plusieurs volumes?

Supposons que mon archive globale possède une taille de fichier de 24 Mo et que je souhaite la scinder en 3 fichiers, dans la limite de 10 Mo par fichier.
Existe-t-il une API zip qui possède cette fonctionnalité? Ou tout autre moyen intéressant d'y parvenir?

Merci Thollsten

Était-ce utile?

La solution

Vérifiez: http: // salon. javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=38&t=004618

Je ne connais aucune API publique susceptible de vous aider à le faire. (Bien que si vous ne voulez pas le faire par programme, il existe des utilitaires comme WinSplitter qui le feront)

Je ne l'ai pas essayé, mais chaque ZipEntry utilisant ZippedInput / OutputStream a une taille compressée. Vous pouvez obtenir une estimation approximative de la taille du fichier compressé lors de sa création. Si vous avez besoin de 2 Mo de fichiers compressés, vous pouvez arrêter d'écrire dans un fichier une fois que la taille cumulée des entrées est devenue 1,9 Mo, en prenant 0,1 Mo pour le fichier manifeste et d'autres éléments spécifiques au fichier zip. En résumé, vous pouvez écrire un wrapper sur le ZippedInputStream comme suit:

import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.io.FileOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ChunkedZippedOutputStream {

    private ZipOutputStream zipOutputStream;

    private String path;
    private String name;

    private long currentSize;
    private int currentChunkIndex;
    private final long MAX_FILE_SIZE = 16000000; // Whatever size you want
    private final String PART_POSTFIX = ".part.";
    private final String FILE_EXTENSION = ".zip";

    public ChunkedZippedOutputStream(String path, String name) throws FileNotFoundException {
        this.path = path;
        this.name = name;
        constructNewStream();
    }

    public void addEntry(ZipEntry entry) throws IOException {
        long entrySize = entry.getCompressedSize();
        if((currentSize + entrySize) > MAX_FILE_SIZE) {
            closeStream();
            constructNewStream();
        } else {
            currentSize += entrySize;
            zipOutputStream.putNextEntry(entry);
        }
    }

    private void closeStream() throws IOException {
        zipOutputStream.close();
    }

    private void constructNewStream() throws FileNotFoundException {
        zipOutputStream = new ZipOutputStream(new FileOutputStream(new File(path, constructCurrentPartName())));
        currentChunkIndex++;
        currentSize = 0;
    }

    private String constructCurrentPartName() {
        // This will give names is the form of <file_name>.part.0.zip, <file_name>.part.1.zip, etc.
        StringBuilder partNameBuilder = new StringBuilder(name);
        partNameBuilder.append(PART_POSTFIX);
        partNameBuilder.append(currentChunkIndex);
        partNameBuilder.append(FILE_EXTENSION);
        return partNameBuilder.toString();
    }
}

Le programme ci-dessus n'est qu'un indice de l'approche et ne constitue en aucun cas une solution finale .

Autres conseils

Si l'objectif est de rendre la sortie compatible avec pkzip et winzip, je ne suis au courant d'aucune bibliothèque open source qui le fasse. Nous avions une exigence similaire pour l'une de nos applications et j'ai fini par écrire notre propre implémentation (compatible avec le standard zip). Si je me souviens bien, la chose la plus difficile pour nous était que nous devions générer les fichiers individuels à la volée (la plupart des utilitaires zip fonctionnent de la manière suivante: ils créent le gros fichier zip, puis reviennent en arrière et le divisent plus tard - c'est beaucoup plus facile à utiliser. Il a fallu environ une journée pour écrire et deux jours pour déboguer.

La norme zip explique à quoi doit ressembler le format de fichier. Si vous n'avez pas peur de retrousser vos manches, c'est tout à fait faisable. Vous devez implémenter vous-même un générateur de fichier zip, mais vous pouvez utiliser la classe Deflator de Java pour générer les flux de segment pour les données compressées. Vous devrez générer vous-même les en-têtes de fichier et de section, mais ce ne sont que des octets - rien de trop difficile une fois que vous avez plongé.

Voici la spécification de zip - la section K contient les informations que vous recherchez. en particulier, mais vous devrez également lire A, B, C et F. Si vous avez affaire à de très gros fichiers (nous l'étions), vous devrez aussi vous lancer dans le Zip64, mais pour 24 Mo, ça va.

Si vous voulez plonger dedans et l'essayer, si vous rencontrez des questions, postez-les en retour et je verrai si je peux fournir des indications.

Le code ci-dessous est ma solution pour scinder le fichier zip dans la structure de répertoires en morceaux en fonction de la taille souhaitée. J'ai trouvé les réponses précédentes utiles, donc je voulais contribuer avec une approche similaire mais légèrement plus nette. Ce code fonctionne pour moi en fonction de mes besoins spécifiques, et j’estime qu’il ya place à amélioration.

private final static long MAX_FILE_SIZE = 1000 * 1000 * 1024; //  around 1GB 
private final static String zipCopyDest =  "C:\\zip2split\\copy";

public static void splitZip(String zipFileName, String zippedPath, String coreId) throws IOException{

    System.out.println("process whole zip file..");
    FileInputStream fis  = new FileInputStream(zippedPath);
    ZipInputStream zipInputStream = new ZipInputStream(fis);
    ZipEntry entry = null;
    int currentChunkIndex = 0;
    //using just to get the uncompressed size of the zipentries
    long entrySize = 0;
    ZipFile zipFile = new ZipFile(zippedPath);
    Enumeration enumeration = zipFile.entries();

    String copDest = zipCopyDest + "\\" + coreId + "_" + currentChunkIndex +".zip";

    FileOutputStream fos = new FileOutputStream(new File(copDest));
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    ZipOutputStream zos = new ZipOutputStream(bos);
    long currentSize = 0; 

    try {
        while ((entry = zipInputStream.getNextEntry()) != null && enumeration.hasMoreElements()) {

            ZipEntry zipEntry = (ZipEntry) enumeration.nextElement();
            System.out.println(zipEntry.getName());
            System.out.println(zipEntry.getSize());
            entrySize = zipEntry.getSize();

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            //long entrySize = entry.getCompressedSize();
            //entrySize = entry.getSize(); //gives -1

            if((currentSize + entrySize) > MAX_FILE_SIZE) {
                zos.close();
                //construct a new stream
                //zos = new ZipOutputStream(new FileOutputStream(new File(zippedPath, constructCurrentPartName(coreId))));
                currentChunkIndex++;
                zos = getOutputStream(currentChunkIndex, coreId);
                currentSize = 0;

            }else{
                currentSize += entrySize;
                zos.putNextEntry(new ZipEntry(entry.getName()));
                byte[] buffer = new byte[8192];
                int length = 0;
                while ((length = zipInputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, length);
                }

                byte[] unzippedFile = outputStream.toByteArray();
                zos.write(unzippedFile);
                unzippedFile = null;
                outputStream.close();
                zos.closeEntry();
            }
            //zos.close();
        }
    } finally {
        zos.close();
    }


}

 public static ZipOutputStream getOutputStream(int i, String coreId) throws IOException {
     System.out.println("inside of getOutputStream()..");
     ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipCopyDest + "\\" + coreId + "_" +  i +".zip"));   
    // out.setLevel(Deflater.DEFAULT_COMPRESSION);
     return out;
 }

public static void main(String args[]) throws IOException{
    String zipFileName = "Large_files _for_testing.zip";
    String zippedPath= "C:\\zip2split\\Large_files _for_testing.zip";
    String coreId = "Large_files _for_testing";
    splitZip(zipFileName, zippedPath, coreId);
}

Pour ce que cela vaut, j'aime utiliser try-with-resources partout. Si vous aimez ce modèle, vous aimerez ceci. Cela résout également le problème des pièces vides si les entrées sont plus grandes que la taille de la pièce souhaitée. au moins aurez autant de parties que d’entrées dans le pire des cas.

Dans:

  

my-archive.zip

Out:

  

my-archive.part1of3.zip
  my-archive.part2of3.zip
  my-archive.part3of3.zip

Remarque: j'utilise la journalisation et Apache Commons FilenameUtils, mais n'hésitez pas à utiliser ce que vous avez dans votre boîte à outils.

/**
 * Utility class to split a zip archive into parts (not volumes)
 * by attempting to fit as many entries into a single part before
 * creating a new part. If a part would otherwise be empty because
 * the next entry won't fit, it will be added anyway to avoid empty parts.
 *
 * @author Eric Draken, 2019
 */
public class Zip
{
    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    private static final String ZIP_PART_FORMAT = "%s.part%dof%d.zip";

    private static final String EXT = "zip";

    private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );

    /**
     * Split a large archive into smaller parts
     *
     * @param zipFile             Source zip file to split (must end with .zip)
     * @param outZipFile          Destination zip file base path. The "part" number will be added automatically
     * @param approxPartSizeBytes Approximate part size
     * @throws IOException Exceptions on file access
     */
    public static void splitZipArchive(
        @NotNull final File zipFile,
        @NotNull final File outZipFile,
        final long approxPartSizeBytes ) throws IOException
    {
        String basename = FilenameUtils.getBaseName( outZipFile.getName() );
        Path basePath = outZipFile.getParentFile() != null ? // Check if this file has a parent folder
            outZipFile.getParentFile().toPath() :
            Paths.get( "" );
        String extension = FilenameUtils.getExtension( zipFile.getName() );
        if ( !extension.equals( EXT ) )
        {
            throw new IllegalArgumentException( "The archive to split must end with ." + EXT );
        }

        // Get a list of entries in the archive
        try ( ZipFile zf = new ZipFile( zipFile ) )
        {
            // Silliness check
            long minRequiredSize = zipFile.length() / 100;
            if ( minRequiredSize > approxPartSizeBytes )
            {
                throw new IllegalArgumentException(
                    "Please select a minimum part size over " + minRequiredSize + " bytes, " +
                        "otherwise there will be over 100 parts."
                );
            }

            // Loop over all the entries in the large archive
            // to calculate the number of parts required
            Enumeration<? extends ZipEntry> enumeration = zf.entries();
            long partSize = 0;
            long totalParts = 1;
            while ( enumeration.hasMoreElements() )
            {
                long nextSize = enumeration.nextElement().getCompressedSize();
                if ( partSize + nextSize > approxPartSizeBytes )
                {
                    partSize = 0;
                    totalParts++;
                }
                partSize += nextSize;
            }

            // Silliness check: if there are more parts than there
            // are entries, then one entry will occupy one part by contract
            totalParts = Math.min( totalParts, zf.size() );

            logger.debug( "Split requires {} parts", totalParts );
            if ( totalParts == 1 )
            {
                // No splitting required. Copy file
                Path outFile = basePath.resolve(
                    String.format( ZIP_PART_FORMAT, basename, 1, 1 )
                );
                Files.copy( zipFile.toPath(), outFile );
                logger.debug( "Copied {} to {} (pass-though)", zipFile.toString(), outFile.toString() );
                return;
            }

            // Reset
            enumeration = zf.entries();

            // Split into parts
            int currPart = 1;
            ZipEntry overflowZipEntry = null;
            while ( overflowZipEntry != null || enumeration.hasMoreElements() )
            {
                Path outFilePart = basePath.resolve(
                    String.format( ZIP_PART_FORMAT, basename, currPart++, totalParts )
                );
                overflowZipEntry = writeEntriesToPart( overflowZipEntry, zf, outFilePart, enumeration, approxPartSizeBytes );
                logger.debug( "Wrote {}", outFilePart );
            }
        }
    }

    /**
     * Write an entry to the to the outFilePart
     *
     * @param overflowZipEntry    ZipEntry that didn't fit in the last part, or null
     * @param inZipFile           The large archive to split
     * @param outFilePart         The part of the archive currently being worked on
     * @param enumeration         Enumeration of ZipEntries
     * @param approxPartSizeBytes Approximate part size
     * @return Overflow ZipEntry, or null
     * @throws IOException File access exceptions
     */
    private static ZipEntry writeEntriesToPart(
        @Nullable ZipEntry overflowZipEntry,
        @NotNull final ZipFile inZipFile,
        @NotNull final Path outFilePart,
        @NotNull final Enumeration<? extends ZipEntry> enumeration,
        final long approxPartSizeBytes
    ) throws IOException
    {
        try (
            ZipOutputStream zos =
                new ZipOutputStream( new FileOutputStream( outFilePart.toFile(), false ) )
        )
        {
            long partSize = 0;
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            while ( overflowZipEntry != null || enumeration.hasMoreElements() )
            {
                ZipEntry entry = overflowZipEntry != null ? overflowZipEntry : enumeration.nextElement();
                overflowZipEntry = null;

                long entrySize = entry.getCompressedSize();
                if ( partSize + entrySize > approxPartSizeBytes )
                {
                    if ( partSize != 0 )
                    {
                        return entry;    // Finished this part, but return the dangling ZipEntry
                    }
                    // Add the entry anyway if the part would otherwise be empty
                }
                partSize += entrySize;
                zos.putNextEntry( entry );

                // Get the input stream for this entry and copy the entry
                try ( InputStream is = inZipFile.getInputStream( entry ) )
                {
                    int bytesRead;
                    while ( (bytesRead = is.read( buffer )) != -1 )
                    {
                        zos.write( buffer, 0, bytesRead );
                    }
                }
            }
            return null;    // Finished splitting
        }
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top