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



我正在下载包含XML的压缩文件,由于延迟要求,我希望避免在操作压缩文件之前将其写入磁盘。然而,java.util.zip对我来说还不够。如果不将其转换为流,就无法说"这是一个zip文件的字节数组,请使用它",而ZipInputStream是不可靠的,因为它会扫描条目头(请参阅下面的EDIT讨论,了解其不可靠的原因)。

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

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

编辑

ApacheCommons-Zip库对使用Stream(包括他们的解决方案和Java)所遇到的一些问题进行了很好的总结。根据维基百科和个人经验,我将进一步补充,条目标题上的大小和crc字段可能不会被填充(我在这些字段中有-1的文件)。感谢centic提供此链接。

此外,让我引用维基百科上关于的主题

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

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

最终编辑

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

编辑:另一个建议。。。

从Apache Commons实现中查看ZipFile,似乎不难为您的项目有效地分叉。在字节数组周围创建一个包装器,其中包含所需的RandomAccessFileAPI的所有部分(我认为没有太多)。您已经表示,与ZipFile相比,您更喜欢该接口,那么为什么不采用该接口呢?

我们对您的项目了解不多,不知道这是否会引发任何法律问题-即使您提供了详细信息,我也怀疑这里的任何人是否能够提供良好的法律建议-但我怀疑这个解决方案不需要超过一两个小时就能启动并发挥作用,我怀疑您对此有合理的信心。


EDIT:这可能是一个更有效的答案。。。

如果你担心条目不连续,但又不想自己处理所有压缩端,你可以考虑一个选项,有效地重写数据。创建一个新的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。EDIT:看起来ZipArchiveStream也没有从中央目录读取。我希望它能提前使用markSupported进行检查,但它似乎没有…

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

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

这与入口数据是可选的不同。这是说在尴尬的地方可能有额外的数据,而不是说条目可能完全丢失。它基本上是说,不应该假设条目是连续的。我可以很高兴地承认,ZipInputStream可能没有读取文件末尾的中心目录,但查找这样做的代码与查找处理不存在的条目数据的代码不同。

然后你写:

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

。。。这表明您想要处理无效zip文件的代码。与此结合:

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

这意味着你要的代码应该处理zip文件,这些文件是无效的,你甚至无法预测。它有多无效,你才能拒绝它?如果我给你1000个随机字节,而根本没有尝试将它们作为zip文件,你到底会怎么处理它?

基本上,在判断某个特定库是否是有效的解决方案之前,您需要更严格地确定问题。从不同的地方收集一组zip文件是合理的,这些文件在众所周知的情况下可能是无效的,并说"我必须能够支持所有这些。"稍后,如果结果不够好,你可能需要做一些工作。但是,能够支持任何东西,无论多么破碎,都不是一个有效的要求。

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.

我将使用Apache库的commons压缩,请参阅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)。

一个例子是使用Java7NIOAPI,它现在提供了一个SPI,用于通过FileSystemProvider实现文件系统。看起来ShrinkWrap文件系统实现了这个SPI。

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

我第一次记得在这篇文章中读到了Java中的内存中文件系统,除了指出Commons VFS和JBoss Microcontainer等解决方案外,它还为NetBeans IDE提供了一个很好的示例用例。

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

最新更新