Java中的多线程解压缩
所以,我尝试用Java对zip文件进行只读访问,以多线程方式进行解压缩,因为我的标准简单单线程ZipFile/ZipEntry解决方案使用了枚举和inputstreams,结果是只需五秒钟就可以将一个50兆ZipFile解压缩到内存中,而不解压缩的话,我的磁盘读取它最多需要一秒钟 然而,整个JavaZIP库被同步到了令人难以置信的讨厌的程度,毫无疑问,这是因为它都是在同一个代码中为读/写/等而抽象的,而不是具有高效的非同步只读代码 我看过第三方Java库,它们要么都是比使用象枪射击苍蝇更糟糕的大型VFS库,要么它们之所以具有性能优势,唯一的原因是它们具有多线程,以至于大多数线程都阻塞了磁盘IO 我所要做的就是将一个zipfile拉入一个字节[],分叉一些线程,然后处理它。任何事情都不需要任何同步,因为我在内存中单独使用的每个解压缩文件都没有交互Java中的多线程解压缩,java,multithreading,zlib,inflate,Java,Multithreading,Zlib,Inflate,所以,我尝试用Java对zip文件进行只读访问,以多线程方式进行解压缩,因为我的标准简单单线程ZipFile/ZipEntry解决方案使用了枚举和inputstreams,结果是只需五秒钟就可以将一个50兆ZipFile解压缩到内存中,而不解压缩的话,我的磁盘读取它最多需要一秒钟 然而,整个JavaZIP库被同步到了令人难以置信的讨厌的程度,毫无疑问,这是因为它都是在同一个代码中为读/写/等而抽象的,而不是具有高效的非同步只读代码 我看过第三方Java库,它们要么都是比使用象枪射击苍蝇更糟糕的大
为什么这会如此困难?用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)
循环中使用关闭的文件从头开始完整的迭代):
- 使用
提取整个(50兆欧,英寸 这种情况下)将文件压缩为DataInputStream.readFully
字节[]
- 生成工作线程(每个物理CPU核心一个,在我的示例中为4个)
每一个都接受这个
,并创建一个字节[]
。第一个工作进程跳过0 条目,第二个跳过1,第二个跳过2,等等,所以它们都是 相互抵消一个。工作线程不同步 所以他们都有自己的zip文件的本地副本 元数据等等。这是线程安全的,因为zip文件是 只读和工作进程不共享解压缩数据ZipInputStream(ByteArrayInputStream)
- 每个工作线程读取并处理一个条目,然后跳过 足够多的条目,使它们再次被一个偏移。所以第一个 线程读取条目0,4,8…,第二个读取条目1,5,9…,依此类推 第四
- 所有工人都用.join()拉回来
- 将zip文件读入
,而无需解压缩 (仅IO)每次迭代的平均时间为0.1秒字节[]
- 正常情况下,直接在基础文件上使用直ZipFile, 产生0.5秒的初始峰值,然后是0.26秒的平均值 之后每次迭代的秒数(从结束后的新鲜开始 上一个ZipFile)
- 将ZipFile读入一个
,创建一个字节[]
不带多线程 总之,会导致0.3秒的初始峰值,然后是 此后每次迭代平均0.26秒,表明 磁盘缓存对渲染随机访问和 初始读取等效值ZipInputStream(ByteArrayInputStream)
- 将ZipFile读入
,生成4个工作线程 使用如上所述的字节[]
,并等待它们 完成后,将每分钟的平均时间降低到0.1秒 迭代字节[]
考虑到这项黑客工作的出色表现,想象一下本机Java zip reader类在没有内置同步的情况下可能会有多大的加速。正如您所注意到的,ZipFile中的所有方法都是同步的。但这只会阻止多个线程在不同的
ZipFile
实例上同时运行,这些实例是为磁盘上的同一个ZipFile打开的
如果希望多个线程以可伸缩的方式读取同一zipfile,则必须为每个线程打开一个zipfile
实例。这样,ZipFile
方法中的每线程锁不会阻止除一个线程外的所有线程同时从ZipFile读取。这还意味着,当每个线程在完成读取后关闭ZipFile
时,它们会关闭自己的实例,而不是共享实例,因此在第二次和后续关闭时不会出现异常
Protip:如果你真的关心速度,你可以通过读取第一个ZipFile实例中的所有ZipEntry
对象,并与所有线程共享它们来获得更高的性能,以避免在读取ZipEntry时重复工作<