在zip文件中插入已经放气的文件

时间:2018-07-27 18:56:32

标签: java android performance zip compression

我一直在寻找这个问题,但是找不到我可以使用的任何答复。

是否可以(使用 java )在 zip文件中包含已经缩小的新文件这样,当我解压缩 zip文件后,这些放气的文件会被充气,就像它们被“充气” 到zip工具一样,合并到要压缩的zip中(根据https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipOutputStream.html#setMethod-int-在ZipEntry中为“ DEFLETED”)。在那种情况下,怎么办?

我从Google和其他搜索者那里获得的有关使用zip文件进行Java压缩的大多数信息可以在以下位置恢复:

我使用JZlib偏转文件(但是您可以使用任何其他库。例如,http://www.avajava.com/tutorials/lessons/how-do-i-deflate-and-inflate-a-file.html

您可以说,我尝试插入已经放气的文件时,它们会再次放气(使用ZipEntry中的DEFLETED方法,这是默认设置),并且当zip文件解压缩时,这些文件被放气到以前已经放气的状态。

从oracle看ZipOutputStream.java的源,您可以看到有两种方法可以将条目添加到zip中:

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

我想要的是在zip文件中将已放气的条目添加为STORED,但是一旦添加它们,就可以更改自己的zip文件中的信息,就像处理DEFLATED 一样。您知道任何图书馆或其他替代品可以轻松做到吗?我当时想让我自己的ZipOutputStream继承自jdk ZipOutputStream并重写实现此技巧的方法,但是根据这种想法对方法进行“快速复制粘贴和修改”-只是具有“可以工作”的感觉-也没有按照我的希望工作。

之所以要使用此选项,是为了根据需要动态压缩zip文件中的大量文件。 我不确定这是否可以节省时间和CPU,并将压缩后的文件保存在数据库中,并随时选择需要进行压缩的文件。

非常感谢

5 个答案:

答案 0 :(得分:1)

zip格式并不是很复杂,因此您应该只提取缩小的数据并在其周围编写自己的zip文件头。该格式记录在here中。如果要从gzip文件导出压缩后的数据,则还应该已经具有CRC和未压缩的长度。 (如果要转换的每个gzip文件都包含一个deflate流,即是一个gzip成员,并且如果确保未压缩的长度小于2 32 个字节,则可以删除“应该”。)

答案 1 :(得分:0)

我可以想象在putNextEntry(e)write之间发生黑客攻击...,您可以使用e.setMethod。像这样的代码将标头写入putNextEntry的末尾,并决定在write中进行压缩,以访问putNextEntry中给出的条目(不进行复制)。 / p>

在致电closeEntry之前,您可能需要将其后退。

我没有尝试过,因为您可以尝试得更快。


  

我不确定这是否可以节省时间并节省将压缩文件保存在数据库中的CPU

我不明白,但我非常怀疑。

OTOH,当给定*.gz一堆文件时,您的想法似乎也适用,您希望存储它们的纯文本而不进行解压缩和再次压缩。

答案 2 :(得分:0)

最后,我走出了第一步,对jdk源进行了更深入的分析,并花了一些时间调试和修改它:

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

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

如果要尝试,必须在Test类的变量PATH_ZIP_DIR,PATH_IN_DIR,PATH_TMP_DIR中指定所需的路径。要压缩的文件来自PATH_IN_DIR Directoy,以及在PATH_ZIP_DIR中创建的zip文件。测试类很短,也不太复杂(尽管也像ExtraZipOutputStream类一样是草稿)。我在PATH_IN_DIR中使用了简单的文本文件来测试其压缩率。如您所见,在此类中,deflate()方法是在compress()将文件放入zip文件之前执行deflaton的方法(这要归功于使用STORED的修改过的ExtraZipOutputStream,但就像写入文件相关联的元数据一样它们已由ExtraZipOutputStream本身缩小了。)

在附加到ExtraZipOutputStream.class标题的注释块中,我解释了如何检测对原始代码所做的更改。

答案 3 :(得分:0)

我知道这是一个过时的线程,但是在查看旧文件夹中清理磁盘的过程中,我看到了一个用这个想法完成的“草稿”项目。最后,我们将其丢弃,但如果有人对此感兴趣,我已将其上传到了回购库中。

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

它甚至不是原型,只是一个简短的示例程序,用于说明其工作方式。正如Mark Alder所说,zip格式并不复杂,在他的回复中指出的链接中对此进行了很好的解释。

答案 4 :(得分:0)

我看了一下ZipOutputStream并想到了这个。

我创建了2个类:PreparedZipEntryBuilderPreparedZipEntry

这将为给定的字节序列创建最短的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);
    }
}