在Java中高效读取zip文件

6

我正在处理一个涉及大量数据的项目。 我有很多(数千个)zip文件,每个文件都包含一个简单的txt文件,其中有成千上万行(约80k行)。 我目前的做法如下:

for(File zipFile: dir.listFiles()){
ZipFile zf = new ZipFile(zipFile);
ZipEntry ze = (ZipEntry) zf.entries().nextElement();
BufferedReader in = new BufferedReader(new InputStreamReader(zf.getInputStream(ze)));
...

这样我可以逐行读取文件,但速度明显太慢。 考虑到需要读取的文件和行数很多,我需要以更高效的方式进行读取。
我已经寻找了不同的方法,但没有找到合适的。 我认为应该使用专门用于大量 I/O 操作的 Java NIO API,但我不知道如何在 zip 文件中使用它们。
非常感谢任何帮助。
谢谢,
马尔科

2
你应该首先确定你大部分的时间是花在从zip文件中读取数据,还是处理文本行上。你对文本文件中的每一行都做了什么? - Jack Edmonds
@Jack Edmons 这些行代表信息,我需要将它们拆分以检索行的字段。 - smellyarmpits
1
我建议这里需要花费时间的是处理过程。你需要进行测量。尝试仅进行阅读部分,不进行任何处理。 - user207421
1
你犯了一个经典错误,认为nio API可以让你的代码更快。nio API可以使你的代码更具可扩展性,使用更少的线程处理更多的流,但这并不一定意味着事情会变得更快。 - jtahlborn
这应该不会那么慢。请在您的程序上运行jvisualvm以确定时间花费在哪里,然后更新您的问题。可能是一些微不足道的问题,比如JVM内存不足或者反病毒软件过于热情。 - Thorbjørn Ravn Andersen
显示剩余2条评论
6个回答

4
正确的迭代zip文件的方法
final ZipFile file = new ZipFile( FILE_NAME );
try
{
    final Enumeration<? extends ZipEntry> entries = file.entries();
    while ( entries.hasMoreElements() )
    {
        final ZipEntry entry = entries.nextElement();
        System.out.println( entry.getName() );
        //use entry input stream:
        readInputStream( file.getInputStream( entry ) )
    }
}
finally
{
    file.close();
}

private static int readInputStream( final InputStream is ) throws IOException {
    final byte[] buf = new byte[ 8192 ];
    int read = 0;
    int cntRead;
    while ( ( cntRead = is.read( buf, 0, buf.length ) ) >=0  )
    {
        read += cntRead;
    }
    return read;
}

Zip文件由多个条目组成,每个条目都有一个字段包含当前条目中的字节数。因此,可以轻松迭代所有Zip文件条目而不进行实际数据解压缩。java.util.zip.ZipFile接受一个文件/文件名并使用随机访问来跳转到文件位置。另一方面,java.util.zip.ZipInputStream正在处理流,因此无法自由跳转。这就是为什么它必须读取和解压缩所有zip数据才能到达EOF,并读取下一个条目头部。
这是什么意思呢?如果您已经在文件系统中拥有一个zip文件-无论您的任务如何,请使用ZipFile来处理它。作为奖励,您可以以顺序或随机方式访问zip条目(带有相当小的性能损失)。另一方面,如果您正在处理流,则需要使用ZipInputStream按顺序处理所有条目。
以下是一个示例。一个大小为1.6Gb的zip存档包含三个0.6Gb的条目,在0.05秒内使用ZipFile进行了迭代,使用ZipInputStream则需要18秒。

3
我有许多(数千个)zip文件。 压缩文件大小约为30MB,而zip文件中的txt文件约为60/70 MB。 读取和处理此代码需要很长时间,大约15个小时,但这取决于情况。
我们来进行一些估算。
假设您有5000个文件。 如果处理它们需要15小时,则相当于每个文件约10秒钟。 文件大小约为30MB,因此吞吐量约为3MB / s。
这比ZipFile可以解压缩的速率慢了一个到两个数量级。
要么磁盘存在问题(它们是本地还是网络共享?),要么实际处理时间最长。
确切了解情况的最佳方法是使用性能分析器。

好的,这里是重点。我目前正在处理总文件的一小部分:我真的不记得处理它们所需的时间,也不记得处理所有文件(不仅仅是我拥有的文件)所花费的时间。无论如何,我们谈论的是几个小时。然而,我想知道读取这些文件的最佳有效方法是什么。使用Java nio API,我们可以使用文件通道高效地读取文件,但似乎不能对zip文件进行操作。如果您知道是否有可能使用其他类型的压缩文件而不是zip文件,请告诉我。谢谢大家, 马可 - smellyarmpits

1
您可以像这样使用新的文件 API:
Path jarPath = Paths.get(...);
try (FileSystem jarFS = FileSystems.newFileSystem(jarPath, null)) {
    Path someFileInJarPath = jarFS.getPath("/...");
    try (ReadableByteChannel rbc = Files.newByteChannel(someFileInJarPath, EnumSet.of(StandardOpenOption.READ))) {
        // read file
    }
}

这段代码是针对jar文件的,但我认为它也适用于zip文件。


不是答案。他声称他花费的时间是阅读文件,而不是寻找它们。 - user207421
OP明确要求使用“java nio APIs”方法。 仔细想想,OP可能正在寻找java.nio.channels方法而不是java.nio.file。 - Puce
我已经更新了我的示例,也使用了java.nio.channels API。虽然我没有进行任何性能分析,也不知道它是否会在这种情况下有所帮助。话虽如此,java.nio.file是Java SE 7中首选的API。 - Puce

0
你可以尝试这段代码。
try
    {

        final ZipFile zf = new ZipFile("C:/Documents and Settings/satheesh/Desktop/POTL.Zip");

        final Enumeration<? extends ZipEntry> entries = zf.entries();
        ZipInputStream zipInput = null;

        while (entries.hasMoreElements())
        {
            final ZipEntry zipEntry=entries.nextElement();
            final String fileName = zipEntry.getName();
        // zipInput = new ZipInputStream(new FileInputStream(fileName));
            InputStream inputs=zf.getInputStream(zipEntry);
            //  final RandomAccessFile br = new RandomAccessFile(fileName, "r");
                BufferedReader br = new BufferedReader(new InputStreamReader(inputs, "UTF-8"));
                FileWriter fr=new FileWriter(f2);
            BufferedWriter wr=new BufferedWriter(new FileWriter(f2) );

            while((line = br.readLine()) != null)
            {
                wr.write(line);
                System.out.println(line);
                wr.newLine();
                wr.flush();
            }
            br.close();
            zipInput.closeEntry();
        }


    }
    catch(Exception e)
    {
        System.out.print(e);
    }
    finally
    {
        System.out.println("\n\n\nThe had been extracted successfully");

    }

这段代码运行良好。


0

异步解包和同步处理

借鉴了Java Performance中的建议,类似于Wasim Wani的答案Satheesh Kumar的答案:迭代ZIP条目以获取每个条目的InputStream并对其进行操作,我构建了自己的解决方案。

在我的情况下,处理是瓶颈,因此我在开始时大量启动并行提取,迭代entries.hasMoreElements(),并将每个结果放置在ConcurrentLinkedQueue中,然后从处理线程中消耗它们。我的ZIP文件包含表示序列化Java对象的一组XML文件,因此我的“提取”包括反序列化对象,并且这些反序列化的对象是放置在队列中的对象。

对我来说,与以前顺序获取ZIP中每个文件并处理它的方法相比,这具有一些优点:

  1. 更具吸引力的是:总时间减少10%
  2. 文件的发布提前了
  3. 整个RAM的分配速度更快,因此如果RAM不足,则会更快地失败(在十几分钟而不是一个多小时内);请注意,我在处理后保留的内存量与未压缩的文件占用的内存量相当接近,否则最好按顺序解压缩和丢弃以保持内存占用量较低
  4. 解压缩和反序列化似乎具有很高的CPU使用率,因此完成得越快,您就能更快地获得用于处理的CPU,这才是真正重要的

有一个缺点:包括并行性时,流程控制会变得稍微复杂一些。


0

英特尔已经制作了一个改进版的zlib,Java内部使用它来执行压缩/解压操作。这需要你使用英特尔的IPP补丁来修补zlib源代码。 我做了一个基准测试,显示吞吐量提高了1.4倍到3倍。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接