使用 ZipFileSystem 压缩一个巨大的文件夹会导致 OutOfMemoryError

2022-09-03 00:38:24

该软件包通过将zip文件视为文件系统来处理它们。具有处理zip文件的美丽方式。这使我们能够像处理普通文件一样处理zip文件内容。因此,只需将所有文件复制到zip文件中,即可实现压缩整个文件夹。由于子文件夹也需要复制,因此我们需要一个访问者:java.nioFiles.copy

 private static class CopyFileVisitor extends SimpleFileVisitor<Path> {
    private final Path targetPath;
    private Path sourcePath = null;
    public CopyFileVisitor(Path targetPath) {
        this.targetPath = targetPath;
    }

    @Override
    public FileVisitResult preVisitDirectory(final Path dir,
    final BasicFileAttributes attrs) throws IOException {
        if (sourcePath == null) {
            sourcePath = dir;
        } else {
        Files.createDirectories(targetPath.resolve(sourcePath
                    .relativize(dir).toString()));
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(final Path file,
    final BasicFileAttributes attrs) throws IOException {
    Files.copy(file,
        targetPath.resolve(sourcePath.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING);
    return FileVisitResult.CONTINUE;
    }
}

这是一个简单的“递归复制目录”访问者。它用于以递归方式复制目录。但是,使用 ,我们也可以使用它将目录复制到 zip 文件中,如下所示:ZipFileSystem

public static void zipFolder(Path zipFile, Path sourceDir) throws ZipException, IOException
{
    // Initialize the Zip Filesystem and get its root
    Map<String, String> env = new HashMap<>();
    env.put("create", "true");
    URI uri = URI.create("jar:" + zipFile.toUri());       
    FileSystem fileSystem = FileSystems.newFileSystem(uri, env);
    Iterable<Path> roots = fileSystem.getRootDirectories();
    Path root = roots.iterator().next();

    // Simply copy the directory into the root of the zip file system
    Files.walkFileTree(sourceDir, new CopyFileVisitor(root));
}

这就是我所说的压缩整个文件夹的优雅方式。但是,在大型文件夹(约3 GB)上使用此方法时,我会收到一个(堆空间)。使用通常的 zip 处理库时,不会引发此错误。因此,似乎处理副本的方式非常低效:太多要写入的文件保存在内存中,因此会发生。OutOfMemoryErrorZipFileSystemOutOfMemoryError

为什么会这样?使用通常被认为是低效的(就内存消耗而言)还是我在这里做错了什么?ZipFileSystem


答案 1

我查看了ZipFileSystem.java我相信我找到了内存消耗的来源。缺省情况下,实现用作压缩文件的缓冲区,这意味着它受分配给 JVM 的内存量的限制。ByteArrayOutputStream

有一个(未记录的)环境变量,我们可以使用它来使实现使用临时文件()。它的工作原理如下:"useTempFile"

Map<String, Object> env = new HashMap<>();
env.put("create", "true");
env.put("useTempFile", Boolean.TRUE);

更多细节在这里:http://www.docjar.com/html/api/com/sun/nio/zipfs/ZipFileSystem.java.html,有趣的行是96,1358和1362。


答案 2

必须使用 准备 jvm 以允许这些内存量。-Xms {memory} -Xmx {memory}

我建议您检查计算磁盘空间的目录并设置一个限制,1Gb使用内存文件系统,超过1GB使用磁盘文件系统。

另一件事,检查方法的并发性,你不会喜欢超过1个线程压缩3Gb的文件


推荐