Java中的多线程解压缩

时间:2013-12-21 10:17:57

标签: java multithreading zlib inflate

所以,我试图用Java对zip文件进行只读访问,以多线程方式解压缩,因为我的ZipFile / ZipEntry的标准简单单线程解决方案使用枚举和输入流以及什么不是结果只需要花费大约五秒就可以将一个50兆的zipfile解压缩到内存中,这需要一秒钟的时间来让我的磁盘读取而不需要解压缩。

然而,整个Java zip库同步到令人难以置信的令人讨厌的程度,毫无疑问因为它全部被抽象用于读/写等。在相同的代码中,而不是使用有效的非同步只读代码。

我看过第三方Java库,所有这些都是大型VFS库,比使用大象枪射击更糟糕,或者他们有性能优势的唯一原因是他们多线程大多数线程在磁盘IO上阻塞的程度。

我想要做的就是将zipfile拉入byte [],分叉一些线程,并对其进行处理。没有任何理由可以以任何方式进行任何同步,因为我在内存中单独使用的每个解压缩文件都没有交互。

为什么要这么难?

3 个答案:

答案 0 :(得分:2)

使用Java实现这一目标的最快方法是使用NIO。您可以使用MappedByteBuffer直接将文件映射到内存中。

FileChannel channel = FileChannel.open(Paths.get("/path/to/zip"),
    StandardOpenOption.READ);
MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());

现在buffer包含整个文件的内存映射区域。您可以随意使用它,例如将offsetlength传递给线程。我不知道哪个zip lib支持它,但显然你已经有类似的东西了。

仅供参考,我使用50 MB的单个文件存档进行了一些测试,平均花费不到200毫秒就可以通常ZipInputStream来阅读 - 我认为你在这里尝试优化的几乎没有。

答案 1 :(得分:2)

为了后人的缘故,经过一些来回的测试后,我最终使用的答案如下(完整的迭代从头开始,在while (true)循环中关闭文件):

  • 使用DataInputStream.readFully拉出整个(50 meg,in 这种情况下)将zip文件转换为byte[]

  • Spawn工作线程(每个物理CPU核心一个,在我的情况下为4个) 每个人都取byte[]并创建一个 ZipInputStream(ByteArrayInputStream)。第一个工人跳过0 条目,第二个跳过1,第二个跳过2等,所以它们都是 相互抵消一个。工作线程不同步 所有,所以他们都有自己的zip文件的本地副本 元数据和什么不是。这是线程安全的,因为zip文件是 只读且工作人员不共享解压缩数据。

  • 每个工作线程读取一个条目并对其进行处理,然后跳过 足够的条目,以便它们再次被一个偏移。所以第一个 线程读取条目0,4,8 ...,第二个读取1,5,9 ......等等 类推。

  • 所有工人都被拉回来.join()。

我的时间如下:

  • 将zip文件读入byte[],完全没有解压缩 (只是IO)每次迭代平均0.1秒。

  • 直接在基础文件上直接使用ZipFile, 产生0.5秒的初始​​峰值,然后平均为0.26 此后每次迭代的秒数(从关闭后的新鲜开始) 以前的ZipFile)。

  • 将ZipFile读入byte[],创建一个 ZipInputStream(ByteArrayInputStream)没有多线程 完全导致最初的峰值为0.3秒,然后是 此后每次迭代平均0.26秒,显示出 磁盘缓存有效渲染随机访问和 初读等值。

  • 将ZipFile读入byte[],生成4个工作线程 使用上面描述的byte[],并等待他们 结束,每次平均时间缩短到0.1秒 迭代。

所以,通过这种方法判断我已经成功地将中等大小的zipfile与中等强度的计算机一起处理到简单地物理读取文件所需的时间,而不再需要额外的解压缩步骤一点都不明显。显然,在包含数万个条目的巨大zip文件上使用相同的方法仍然会产生巨大的加速。

似乎我并没有尝试优化任何东西,考虑到我将样本文件的处理时间(大约是我需要使用的最大文件的大小)减少到单纯的38% - 线程方法。

考虑到这个黑客工作的效果非常好,想象一下本地Java zip-reader类实际上可以在没有内置同步的情况下实现这一目标的可能加速。

答案 2 :(得分:1)

您注意到,ZipFile中的所有方法都是同步的。但这只会阻止多个线程在为磁盘上相同的zipfile打开的不同 ZipFile实例上同时运行。

如果您希望多个线程以可伸缩的方式从同一个zip文件读取,则必须为每个线程打开一个ZipFile实例。这样,ZipFile方法中的每线程锁定不会阻止只有一个线程一次读取zipfile。这也意味着当每个线程在完成读取后关闭ZipFile时,它们将关闭自己的实例,而不是共享实例,因此您不会在第二次及其后的关闭中遇到异常。

Protip:如果您真的关心速度,可以通过从第一个ZipFile实例读取所有ZipEntry对象并与所有线程共享它们来获得更高的性能,以避免在读取{{1} }每个线程的对象。 ZipEntry对象本身并不绑定到特定的ZipEntry实例,ZipFile仅记录元数据,该元数据将与代表与{{1 }}来自。