Вставка уже спущенных файлов в zip-файл

Я искал эту проблему, но не нашел ответа, который можно было бы использовать.

Возможно ли (с использованием Ява) включить новые файлы, которые являются уже спущен, в zip файл таким образом, чтобы после того, как я декомпрессировать zip-файл, эти сжатые файлы были надуты так же, как если бы их передали «надутые» для инструмента zip и были включены в zip для сжатия (" ОТКАЗАНО "в ZipEntry согласно https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipOutputStream.html#setMethod-int-). В таком случае как?

Большая часть информации о сжатии с помощью java для zip-файлов, которую я получаю от Google и других поисковиков, может быть возобновлена ​​в:

Я отклоняю файлы с помощью JZlib (но вы можете делать что угодно и с любой другой библиотекой. Пример, http://www.avajava.com/tutorials/lessons/how-do-i-deflate-and-inflate-a-file.html)

Как и следовало ожидать из того, что я сказал, когда я пытаюсь вставить уже спущенные файлы, они снова сдуваются (с использованием метода DEFLETED в ZipEntry, который используется по умолчанию), а когда zip-файл распаковывается, файлы сдуваются до в их бывшем уже спущенном состоянии.

Посмотрев на источник ZipOutputStream.java из oracle, вы увидите, что есть два метода добавления записей в zip-архив:

    DEFLATED (an integer set to 20)
    STORED   (an integer set to 10)

Я хочу добавить дефлированные записи как СОХРАНЕННЫЕ в zip-архиве, но как только они будут добавлены, чтобы изменить информацию в собственном zip-архиве, как если бы они были обработаны DEFLATED. Знаете ли вы какую-нибудь библиотеку или альтернативу, чтобы сделать это легко? Я думал создать свой собственный ZipOutputStream, унаследованный от jdk ZipOutputStream и переопределив методы, чтобы сделать трюк, но «быстрое копирование-вставка и модификация» методов в соответствии с этой идеей - просто чтобы иметь ощущение «это может сработать» - тоже не оправдала моих надежд.

Причина, по которой я хотел бы иметь эту опцию, заключается в динамическом сжатии огромного количества файлов в zip-файле по запросу. Я не уверен, может ли это сэкономить время и ЦП, сохраняя спущенные файлы в базе данных. и каждый момент выбирая те файлы, которые запрашиваются для создания zip-архивов.

Большое спасибо,

Привет, Гильермо, вы заметили мое решение от 6 сентября 2020 года в 20:42?

Dave The Dane 11.11.2020 09:29
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
706
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Я мог представить себе взлом ... между putNextEntry(e) и write, вы могли бы использовать e.setMethod. Такой код, как будто он записывает заголовок в конце putNextEntry и решает о сжатии в write, где он обращается к той самой записи, которая была дана в putNextEntry (копирование не производится).

Возможно, вам придется перевернуть его, прежде чем звонить в closeEntry.

Я не пробовал, так как вы можете попробовать намного быстрее.


I am not sure if this could save time and cpu having the deflated files saved in the database

Я не понимаю, но я настроен очень скептически.

OTOH ваша идея выглядит применимой также, когда вам дана куча файлов *.gz, и вы хотите сохранить их открытый текст без их повторного распаковки и сжатия.

Цель - как можно больше избегать сжатия и декомпрессии. Представьте себе огромный репозиторий, единственная функция которого - обслуживать большие наборы сжатых данных, из которых пользователи могут создавать свои собственные (большие) базы данных. Сжать один раз и использовать столько раз, сколько необходимо для создания динамически настраиваемых пакетов данных, а базы данных по запросу могут сэкономить память и объем обработки. Потоки сжатых данных короче, и время их обработки сокращается.

GYLZ 03.08.2018 04:00

Формат zip не слишком сложен, поэтому вам нужно просто взять дефлированные данные и написать вокруг них свои собственные заголовки zip-файла. Формат задокументирован здесь. Если вы получаете дефлированные данные из файлов gzip, тогда у вас уже должны быть CRC и несжатые длины. (Если каждый файл gzip, который вы хотите преобразовать, состоит из одного потока deflate, то есть является одним членом gzip, и если длина несжатого файла гарантированно меньше 232 байтов, тогда вы можете удалить «следует» в этом операторе.)

Я «поигрался» с ним (см. Мое решение ниже) и получил черновой экспериментальный подход. Мне было интересно узнать о возможности того, что jdk рассмотрел сценарий, о котором я просил, чтобы был и «простой» и компактный способ его получения (т.е. просто указание «специального» метода в конфигурации ZipOutputSream или добавление или изменение нескольких предложения относительно фрагмента кода, которые обычно показаны в качестве примеров для сжатия простых файлов)

GYLZ 03.08.2018 04:17

Наконец, я пошел дальше своего первого подхода, глубже проанализировал исходный код jdk и потратил некоторое время на его отладку и изменение:

https://gist.github.com/gylz/b2db94ce55f1829f2e2a2cd498092d46

https://gist.github.com/gylz/284d8b891fc0bbd3161d1ec5929be074

Если вы хотите попробовать, вы должны указать нужные пути в переменных PATH_ZIP_DIR, PATH_IN_DIR, PATH_TMP_DIR в классе Test. Файлы для сжатия берутся из каталога PATH_IN_DIR и zip-файла, созданного в PATH_ZIP_DIR. Тестовый класс короткий и не слишком сложный (несмотря на то, что он также является черновиком класса ExtraZipOutputStream). Я использовал простые текстовые файлы в PATH_IN_DIR, чтобы проверить их сжатие. Как вы можете видеть, в этом классе метод deflate () - это метод, который выполняет deflaton перед тем, как compress () поместит файлы в zip-файл (благодаря модифицированному ExtraZipOutputStream, использующему STORED, но записывающему метаданные, связанные с файлами, как если бы они были сброшены самим ExtraZipOutputStream.)

В блоке комментариев, добавленном к заголовку ExtraZipOutputStream.class, я объясняю, как определить изменения, которые я внес в исходный код.

Ответ принят как подходящий

Я знаю, что это устаревшая тема, но, заглянув в свои старые папки для очистки диска, я увидел «черновой» проект, который я сделал с этой идеей для моей работы. В конце концов, мы от него отказались, но я загрузил его в репо, если кому-то это может быть интересно ...

https://gitlab.com/gylz.mail/dynazip

Это даже не прототип, а всего лишь небольшой пример программы, показывающей, как это будет работать. Как сказал Марк Алдер, формат zip-архива не сложен, и он хорошо объясняется в ссылке, на которую он указал в своем ответе.

Я немного посмотрел на ZipOutputStream и придумал это.

Я создал 2 класса: PreparedZipEntryBuilder и PreparedZipEntry.

Это создаст самый короткий ZipEntry для данной последовательности байтов.

В PreparedZipEntryBuilder я включил несколько тестовых случаев.

Это совершенно неинвазивно, довольно просто и повсюду прокомментировано.

Повеселись.

Вот PreparedZipEntry:

package com.stackoverflow.preparedzipentry;

import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class PreparedZipEntry {

    private final int    originalLength;
    private final long   originalCRC;
    private final int    deflatedLength;
    private final int    bestMethodDeterminedByBuilder;
    private final byte[] preparedEntryBytes;
    private final long   preparedEntryBytesCRC;
    
    /* package private */ PreparedZipEntry(final int originalLength, final long originalCRC, final int deflatedLength, final int method, final byte[] preparedEntryBytes) {

        final CRC32 preparedEntryBytesCRC32 = new CRC32();
        /**/        preparedEntryBytesCRC32.update(preparedEntryBytes);

        /*
         * These 6 Fields are all you need to correctly insert a prepared entry.
         * If desired, they can be written to a File for later use.
         * (in that case you might want to pass the original bytes to this constructor too?)
         */
        this.originalLength                = originalLength;
        this.originalCRC                   = originalCRC;
        this.deflatedLength                = deflatedLength;
        this.bestMethodDeterminedByBuilder = method;
        this.preparedEntryBytes            = preparedEntryBytes;
        this.preparedEntryBytesCRC         = preparedEntryBytesCRC32.getValue();
    }

    /**
     * Writes our PreparedZipEntry to the Outputstream.
     * <p>
     * You may set the FileTimes in the returned ZipEntry.<br>
     * LastModifiedTime will be used to create the Zip-Directory @ EOF
     * 
     * @param   zos
     * @param   entryName
     * @return
     * @throws  IOException
     */
    public ZipEntry writeEntry(final ZipOutputStream zos, final String entryName) throws IOException {

        final ZipEntry entry = new ZipEntry(entryName);

        /*
         * Set the Sizes correctly for the Entry Header & write an Entry for the desired Method...
         */
        entry.setSize          (this.originalLength);
        entry.setCompressedSize(this.deflatedLength);
        entry.setCrc           (this.originalCRC);
        entry.setMethod        (this.bestMethodDeterminedByBuilder);  // Must use this Method (influences LOC-Header construction)
        zos.putNextEntry(entry);

        /*
         * Now set the byte-count to what write(...) is expecting for the prepared bytes and write them as STORED...
         */
        entry.setMethod(ZipEntry.STORED);
        entry.setSize  (this.preparedEntryBytes.length);
        zos.write      (this.preparedEntryBytes);

        /*
         * Now set the CRC to what closeEntry() is expecting for the bytes just STORED & close the Entry...
         */
        entry.setCrc   (this.preparedEntryBytesCRC);
        zos.closeEntry();

        /*
         * Finally, set the Sizes, CRC & Method correctly once more...
         * (ZipOutputStream will use these later to write the Zip-Directory @ EOF)
         */
        entry.setSize  (this.originalLength);
        entry.setCrc   (this.originalCRC);
        entry.setMethod(this.bestMethodDeterminedByBuilder);

        return entry;
    }
}

А вот и Строитель:

package com.stackoverflow.preparedzipentry;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;

import java.time.Instant;

import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class PreparedZipEntryBuilder {

    public static PreparedZipEntry of(final byte[] entryBytes) {

        final int    entryBytesLength    = entryBytes.length;
        final long   entryBytesCRC       = getCRC32(entryBytes);

        final byte[] deflatedBytes       = getDeflatedBytes(entryBytes);
        final int    deflatedBytesLength = deflatedBytes.length;

        /*
         * Depending on how well the bytes compress, generate an uncompressed or compressed PreparedZipEntry...
         */
        if (deflatedBytesLength < entryBytesLength) {
            /*
             * Compressed length was less than the uncompressed length
             */
            return new PreparedZipEntry(entryBytesLength, entryBytesCRC, deflatedBytesLength, ZipEntry.DEFLATED, deflatedBytes);
        } else {
            return new PreparedZipEntry(entryBytesLength, entryBytesCRC, entryBytesLength,    ZipEntry.STORED,   entryBytes);
            /*
             * Uncompressed was shorter!
             */
        }
    }

    private static byte[] getDeflatedBytes(final byte[] bytes) {

        try(final ByteArrayOutputStream baos = new ByteArrayOutputStream((int) (bytes.length * 0.4 /* Guess: 40% */));
            final          OutputStream bos  = new  BufferedOutputStream(baos);
            final  DeflaterOutputStream dos  = new  DeflaterOutputStream(bos, new Deflater(Deflater.BEST_COMPRESSION, true)))
        {
            dos.write(bytes, 0, bytes.length);
            dos.close();

            return baos.toByteArray();
        }
        catch (final           IOException cannotHappen) {
            throw new UncheckedIOException(cannotHappen);
        }
    }

    private static long getCRC32(final byte[] bytes) {

        final CRC32 crc32 = new CRC32();
        /**/        crc32.update(bytes);
        return      crc32.getValue();
    }

    private static ZipEntry writeRegularEntryUnknown(final ZipOutputStream zos, final String entryName, final byte[] entryBytes) throws IOException {

        final byte[] deflatedBytes       = getDeflatedBytes(entryBytes);

        final ZipEntry entry = new ZipEntry(entryName);
        /**/           entry.setLastModifiedTime(FileTime.from(Instant.now())); // For test: not absolutely necessary.

        entry.setSize          (entryBytes   .length);
        entry.setCompressedSize(deflatedBytes.length);
        entry.setCrc           (getCRC32(entryBytes));
        entry.setMethod        (ZipEntry.DEFLATED);

        zos.putNextEntry(entry);
        zos.write       (entryBytes, 0, entryBytes.length);
        zos.closeEntry();

        return entry;
    }

    private static ZipEntry writeRegularEntryKnown(final ZipOutputStream zos, final String entryName, final byte[] entryBytes) throws IOException {

        final ZipEntry entry = new ZipEntry(entryName);
        /**/           entry.setLastModifiedTime(FileTime.from(Instant.now())); // For test: not absolutely necessary.
        /**/           entry.setMethod        (ZipEntry.DEFLATED);

        zos.putNextEntry(entry);
        zos.write       (entryBytes, 0, entryBytes.length);
        zos.closeEntry();

        return entry;
    }

    public static void main(String[] args) throws IOException {

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ZipOutputStream        zos = new ZipOutputStream(baos);
        /**/                         zos.setLevel(Deflater.BEST_COMPRESSION);

        final String string0s = ">00000000<";
        final String string09 = "0123456789";
        final String stringaz = "abcdefghijklmnopqrstuvwxyz";
        final String stringAZ = stringaz.toUpperCase();

        int z = 1000;

        for (final String entryText : new String[] {
                "Short",
                "Compresses poorly : "    + string09 + stringaz + stringAZ,
                "Compresses well : "      + string09 + string09 + string09 + string09 + string09 + string09,
                "Compresses very well : " + string0s + string0s + string0s + string0s + string0s + string0s,
                "-----------------------------------------------------------------------------------------", // <- Compresses extremely well
        }) {
            for (final String ab : new String[] {"a", "b"}) {

                final PreparedZipEntry prepared =  PreparedZipEntryBuilder.of(entryText.getBytes());
                /**/                   prepared.writeEntry     (zos, z++ + ab + "_Prepared");
                /**/                   writeRegularEntryKnown  (zos, z++ + ab + "_Regular_KnownLength",   entryText.getBytes());
                final ZipEntry  last = writeRegularEntryUnknown(zos, z++ + ab + "_Regular_unknownLength", entryText.getBytes());
                /**/            last.setLastModifiedTime(FileTime.from(Instant.now().minusSeconds(99 * z)));
            }
        }

        zos.close();

        final byte[] mainBytes = baos.toByteArray();

        final Path path = Paths.get("MultiMemberPredeflated.zip");

        Files.deleteIfExists(path);
        Files.write         (path, mainBytes, StandardOpenOption.CREATE);
    }
}

Другие вопросы по теме