在Java中解压缩内存中的ZIP文件

我正在下载包含XML的压缩文件,我想避免在操作它们之前将zip文件写入磁盘,因为延迟要求。 但是, java.util.zip对我来说还不够。 没有办法说“这里是zip文件的字节数组,使用它”而不将其转换为流, ZipInputStream不可靠,因为它会扫描条目标题(请参阅EDIT下面的讨论,了解为什么不可靠) 。

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

假设ZipInputStream不起作用,在没有条目标题的情况下,我该怎么做才能解决这个问题? 我正在使用维基百科的定义 ,其中包括如何正确解压缩zip文件(下面引用)作为标准。

编辑

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

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

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

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

最终编辑

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

编辑:另一个建议……

从Apache Commons实现看ZipFile ,看起来为您的项目有效地分叉它并不会太难 。 在你的字节数组周围创建一个包装器,它包含所需的RandomAccessFile API的所有部分(我认为不是很多)。 您已经表示您更喜欢ZipFile的界面,那么为什么不选择呢?

我们对您的项目知之甚少,不知道这是否会引发任何法律问题 – 即使您提供了详细信息,我怀疑这里的任何人都能提供良好的法律建议 – 但我怀疑它不会超过一两个小时的时间来解决这个问题并开始工作,我怀疑你对此有合理的信心。


编辑:这可能是一个稍微有效的答案……

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

完成后,将ByteArrayOutputStream转换为新的 byte[] ,将其包装在ByteArrayInputStream ,然后将其传递给ZipInputStreamZipArchiveInputStream

根据您的目的,您可能甚至不需要做那么多 – 您可以通过创建一个“迷你”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文件。 你能够拒绝它有多么无效? 如果我给你1000个随机字节,而根本没有尝试将它们作为一个zip文件,你到底会用它做什么?

基本上,您需要更紧密地解决问题,然后才能确定特定库是否是有效的解决方案。 从各个地方收集一组zip文件是合理的,这些文件可能以易于理解的方式无效,并说“我必须能够支持所有这些”。 如果事实certificate那不够好,你可能需要做一些工作。 但是,为了能够支持任何事情,无论多么破碎,都不是一个有效的要求。

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

它甚至还为HTTP提供文件系统抽象function。

例如:

 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 。

我会使用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(); } 

这个问题听起来类似于如何在内存中创建目录? 伪文件系统/虚拟目录 。 基本上,我的建议是使用更通用的解决方案 – 内存中的虚拟文件系统(我并不是指在操作系统级别,如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提供了一个很好的示例用例。

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