Java中的多线程解压缩

Java中的多线程解压缩,java,multithreading,zlib,inflate,Java,Multithreading,Zlib,Inflate,所以,我尝试用Java对zip文件进行只读访问,以多线程方式进行解压缩,因为我的标准简单单线程ZipFile/ZipEntry解决方案使用了枚举和inputstreams,结果是只需五秒钟就可以将一个50兆ZipFile解压缩到内存中,而不解压缩的话,我的磁盘读取它最多需要一秒钟 然而,整个JavaZIP库被同步到了令人难以置信的讨厌的程度,毫无疑问,这是因为它都是在同一个代码中为读/写/等而抽象的,而不是具有高效的非同步只读代码 我看过第三方Java库,它们要么都是比使用象枪射击苍蝇更糟糕的大

所以,我尝试用Java对zip文件进行只读访问,以多线程方式进行解压缩,因为我的标准简单单线程ZipFile/ZipEntry解决方案使用了枚举和inputstreams,结果是只需五秒钟就可以将一个50兆ZipFile解压缩到内存中,而不解压缩的话,我的磁盘读取它最多需要一秒钟

然而,整个JavaZIP库被同步到了令人难以置信的讨厌的程度,毫无疑问,这是因为它都是在同一个代码中为读/写/等而抽象的,而不是具有高效的非同步只读代码

我看过第三方Java库,它们要么都是比使用象枪射击苍蝇更糟糕的大型VFS库,要么它们之所以具有性能优势,唯一的原因是它们具有多线程,以至于大多数线程都阻塞了磁盘IO

我所要做的就是将一个zipfile拉入一个字节[],分叉一些线程,然后处理它。任何事情都不需要任何同步,因为我在内存中单独使用的每个解压缩文件都没有交互


为什么这会如此困难?

用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
包含整个文件的内存映射区域。您可以使用它执行任何操作,例如,将
偏移量
长度
传递给线程。我不知道哪个zip库支持这个功能,但很明显,您已经有了类似的功能


仅供参考,我测试了一个50MB的单文件归档文件,用通常的
ZipInputStream
读取它平均不到200毫秒-我认为您在尝试优化这里几乎什么都没有

为了子孙后代,经过反复测试,我最终使用的答案如下(在
while(true)
循环中使用关闭的文件从头开始完整的迭代):

  • 使用
    DataInputStream.readFully
    提取整个(50兆欧,英寸 这种情况下)将文件压缩为
    字节[]

  • 生成工作线程(每个物理CPU核心一个,在我的示例中为4个) 每一个都接受这个
    字节[]
    ,并创建一个
    ZipInputStream(ByteArrayInputStream)
    。第一个工作进程跳过0 条目,第二个跳过1,第二个跳过2,等等,所以它们都是 相互抵消一个。工作线程不同步 所以他们都有自己的zip文件的本地副本 元数据等等。这是线程安全的,因为zip文件是 只读和工作进程不共享解压缩数据

  • 每个工作线程读取并处理一个条目,然后跳过 足够多的条目,使它们再次被一个偏移。所以第一个 线程读取条目0,4,8…,第二个读取条目1,5,9…,依此类推 第四

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

我的时间如下:

  • 将zip文件读入
    字节[]
    ,而无需解压缩 (仅IO)每次迭代的平均时间为0.1秒

  • 正常情况下,直接在基础文件上使用直ZipFile, 产生0.5秒的初始峰值,然后是0.26秒的平均值 之后每次迭代的秒数(从结束后的新鲜开始 上一个ZipFile)

  • 将ZipFile读入一个
    字节[]
    ,创建一个
    ZipInputStream(ByteArrayInputStream)
    不带多线程 总之,会导致0.3秒的初始峰值,然后是 此后每次迭代平均0.26秒,表明 磁盘缓存对渲染随机访问和 初始读取等效值

  • 将ZipFile读入
    字节[]
    ,生成4个工作线程 使用如上所述的
    字节[]
    ,并等待它们 完成后,将每分钟的平均时间降低到0.1秒 迭代

因此,我的结论是,通过这种方法,我成功地将中等大小的zipfile和中等功能的计算机的处理时间降低到了物理读取文件所需的时间,而额外的解压缩步骤不再明显。显然,在一个包含成千上万个条目的巨大zip文件上使用相同的方法仍然会产生巨大的加速

考虑到我将示例文件(大约是我需要处理的最大文件的大小)的处理时间减少到了简单单线程方法的38%,我似乎没有试图优化任何内容


考虑到这项黑客工作的出色表现,想象一下本机Java zip reader类在没有内置同步的情况下可能会有多大的加速。

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

如果希望多个线程以可伸缩的方式读取同一zipfile,则必须为每个线程打开一个
zipfile
实例。这样,
ZipFile
方法中的每线程锁不会阻止除一个线程外的所有线程同时从ZipFile读取。这还意味着,当每个线程在完成读取后关闭
ZipFile
时,它们会关闭自己的实例,而不是共享实例,因此在第二次和后续关闭时不会出现异常

Protip:如果你真的关心速度,你可以通过读取第一个ZipFile实例中的所有
ZipEntry
对象,并与所有线程共享它们来获得更高的性能,以避免在读取
ZipEntry时重复工作<