29

我正在下载包含 XML 的压缩文件,并且由于延迟要求,我想避免在操作它们之前将压缩文件写入磁盘。然而,java.util.zip对我来说还不够。没有办法说“这是一个 zip 文件的字节数组,使用它”而不将其转换为流,并且ZipInputStream不可靠,因为它会扫描条目标头(有关不可靠的原因,请参见编辑下面的讨论)。

我还没有访问我将要处理的 zip 文件的权限,所以我不知道我是否能够通过 处理它们ZipInputStream,我需要找到一个适用于任何有效 ZIP 文件的解决方案,因为一旦我投入生产,失败的惩罚会很高。

假设 ZipInputStream 不起作用,在没有条目标头的情况下我能做些什么来解决这个问题?我使用Wikipedia 的定义作为标准,其中包括关于如何正确解压缩 zip 文件(引用如下)的评论。

编辑

Apache Commons Zip 库对使用 Stream(他们的解决方案和 Java 的)的一些问题有很好的描述。我将根据维基百科和个人经验进一步补充,条目标题上的 size 和 crc 字段可能未填写(我在这些字段中有 -1 的文件)。感谢centic提供此链接。

另外,让我引用有关该主题的维基百科:

正确读取 zip 存档的工具必须扫描各个字段的签名,即 zip 中央目录。他们不能扫描条目,因为只有目录指定文件块的开始位置。扫描可能会导致误报,因为该格式不禁止其他数据位于块之间,或包含此类签名的未压缩流。

请注意,ZipInputStream扫描条目,而不是中央目录,这是它的问题。

最终编辑

如果有人有兴趣,可以使用此脚本生成无法ZipInputStream从现有 ZIP 文件读取的有效 ZIP 文件。因此,作为对这个封闭问题的最后编辑,我需要一个可以读取文件的库,例如由该脚本生成的文件。

4

4 回答 4

23

编辑:另一个建议...

ZipFileApache Commons 实现来看,为您的项目有效地分叉似乎并不。在您的字节数组周围创建一个包装器,RandomAccessFile其中包含所需的所有 API 片段(我不认为有很多)。您已经表明您更喜欢界面ZipFile,那么为什么不这样做呢?

我们对您的项目知之甚少,无法知道这是否会引发任何法律问题——即使您提供了详细信息,我怀疑这里的任何人都能够提供良好的法律建议——但我怀疑这不会超过一两个小时让这个解决方案启动并运行,我怀疑你会对它有合理的信心。


编辑:这可能是一个更有成效的答案......

如果您担心条目不连续,但又不想自己处理所有压缩方面,您可以考虑有效地重写数据的选项。创建一个新的ByteArrayOutputStream,并在最后读取中央目录。ZipInputStream对于中央目录中的每个条目,以您认为会满意的格式将条目(标题 + 数据)写入输出流。然后编写一个新的中央目录 - 如果您希望替换有效,您可能需要从头开始执行此操作,但如果您使用的代码您知道实际上不会读取中央目录,您可以只提供原始目录,忽略它可能无效的事实。只要它以正确的签名开始,那可能就足够了:)

完成后,将 theByteArrayOutputStream转换为new byte[],将其包装在 a 中ByteArrayInputStream,然后将其传递给ZipInputStreamor ZipArchiveInputStream

根据您的目的,您甚至可能不需要做那么多 - 您可以通过创建一个“迷你” zip 文件来提取每个文件,一次只从目录中读取一个条目.

确实涉及理解 zip 文件格式,但并不完全 - 只是框架,有效。这不像完全使用现有 API 那样快速简单的解决方法,但不会花费长时间。它不能保证它能够读取所有无效文件(怎么可能?),但它会保护您免受您似乎特别关心的“条目之间的数据”问题。希望这至少是一个有用的想法......


没有办法说“这是一个 zip 文件的字节数组,使用它”

就在这里:

byte[] data = ...;
ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
ZipInputStream zipStream = new ZipInputStream(byteStream);

这就留下了是否ZipInputStream可以处理您将提供的所有 zip 文件的问题——但我不会这么快就把它写下来。

当然,还有其他可用的 API。例如,您可能想查看Apache Commons Compress。即使ZipFile需要一个文件,ZipArchiveInputStream也不需要 - 所以再次,您可以使用ByteArrayInputStream. 编辑:看起来ZipArchiveStream 也没有从中央目录中读取。我希望它会markSupported用来事先检查,但它似乎没有......

编辑:在对问题的评论中,我问你在哪里读到 zip 文件不必包含条目数据。你引用了维基百科:

“正确读取 zip 存档的工具必须扫描各个字段的签名,即 zip 中央目录。它们不能扫描条目,因为只有目录指定文件块的开始位置。扫描可能导致误报,因为格式不'不禁止其他数据位于块之间,或包含此类签名的未压缩流。”

这与条目数据是可选的不同。这是说在尴尬的地方可能有额外的数据,而不是条目可能完全丢失。基本上是说不应假定条目是连续的。我可以很高兴地承认,这ZipInputStream可能不是在读取文件末尾的中央目录,但是查找执行此操作的代码与查找处理不存在的条目数据的代码不同。

然后你写:

我可能会进一步补充说,zip 是否有效不是我关心的问题。使用它是。

...这表明您需要处理无效 zip 文件的代码。结合这个:

我还没有访问我将要处理的 zip 文件,所以我不知道我是否能够通过流处理它们

这意味着您要求的代码应该以您甚至无法预测的方式处理无效的 zip 文件。你能拒绝它有多无效?如果我给你 1000 个随机字节,根本不尝试将它们变成一个 zip 文件,你到底会用它做什么?

基本上,您需要更严格地确定问题,然后才能说出特定库是否是有效的解决方案。从各个地方收集一组 zip 文件是合理的,这些文件可能以众所周知的方式无效,并说“我必须能够支持所有这些”。稍后,如果事实证明这还不够好,您可能需要做一些工作。但是能够支持任何东西,无论多么损坏,都不是一个有效的要求。

于 2012-09-06T06:01:32.697 回答
2

TrueZIP库提供了替代成熟的 zip 实现。

它还具有文件系统抽象功能,即使对于 HTTP也是如此。

例如:

Path path = new TPath(new URI("http://acme.com/download/everything.zip/entry.xml"));
try (InputStream in = Files.newInputStream(path)) {
    // Read archive entry contents here.
    ...
}

因此,如果您只对特定条目感兴趣,它只会下载它们,从而节省带宽和时间。而且您不必编写下载代码。

另见http://truezip.java.net/faq.html#http

于 2012-09-06T06:32:25.543 回答
2

我会使用 Apache 库 commons-compress,见http://commons.apache.org/compress/

它支持通过流读取 Zip 文件,在http://commons.apache.org/compress/zip.html有深入的文档以获取详细文档。它还说明了 Zip 格式固有的一些限制。

示例代码如下所示:

ZipArchiveInputStream zip =
    new ZipArchiveInputStream(inputStream);
try {
    ZipArchiveEntry entry = zip.getNextZipEntry();
    while(entry != null) {
        assertEquals("README", entry.getName());
        ...
        entry = zip.getNextZipEntry();
    }
} finally {
    zip.close();
}
于 2012-09-11T10:11:49.653 回答
1

这个问题听起来类似于如何在内存中创建目录?伪文件系统/虚拟目录。基本上,我的建议是使用更通用的解决方案——内存中的虚拟文件系统(我不是指操作系统级别,如 Linux 的 ramfs/tmpfs)。

一个例子是使用 Java 7 NIO API,它现在提供了一个 SPI,用于通过FileSystemProvider实现文件系统。ShrinkWrap文件系统似乎实现了这个 SPI。

一个更易于访问的选项是使用 Apache Commons VFS 的ram 文件系统:它只需要 Java 5。如果您需要与 Java 5 和 6 兼容,这可能是您最好的选择。

我首先记得从这篇文章中阅读了有关 Java 内存文件系统的内容,除了指出 Commons VFS 和JBoss Microcontainer等解决方案之外,它还为 NetBeans IDE 提供了一个很好的示例用例。

虽然内存中的虚拟文件系统是避免操作系统级文件系统(具有相关的性能优势)的一个很好的通用解决方案,但它可能存在其他缺点,更专业的解决方案可以解决这些缺点。例如,我不确定从多个线程同时使用此文件系统时会如何表现。只要您不访问相同的文件,它就可以正常工作,或者您可能需要创建单独的文件系统(这在资源使用方面可能会令人望而却步)。

于 2012-09-13T07:50:23.160 回答