Сжатие огромной папки с использованием ZipFileSystem приводит к OutOfMemoryError
java.nio
Пакет имеет прекрасный способ обработки zip-файлов, рассматривая их как файловые системы. Это позволяет нам обращаться с содержимым почтового файла как с обычными файлами. Таким образом, архивирование всей папки может быть достигнуто простым использованием Files.copy
скопировать все файлы в zip-файл. Поскольку подпапки также должны быть скопированы, нам нужен посетитель:
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;
}
}
Это простой рекурсивный посетитель. Он используется для рекурсивного копирования каталога. Однако с ZipFileSystem
Мы также можем использовать его для копирования каталога в zip-файл, например так:
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 ГБ) я получаю OutOfMemoryError
(куча места). При использовании обычной библиотеки обработки zip эта ошибка не возникает. Таким образом, кажется, что путь ZipFileSystem
обрабатывает копию очень неэффективно: слишком много файлов для записи хранится в памяти, поэтому OutOfMemoryError
происходит.
Почему это так? Использует ZipFileSystem
вообще считается неэффективным (с точки зрения потребления памяти) или я здесь что-то не так делаю?
2 ответа
Я посмотрел на ZipFileSystem.java и считаю, что нашел источник потребления памяти. По умолчанию реализация использует ByteArrayOutputStream
в качестве буфера для сжатия файлов, что означает, что он ограничен объемом памяти, выделенным для JVM.
Существует (недокументированная) переменная среды, которую мы можем использовать, чтобы реализация использовала временные файлы ("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.
Вы должны подготовить JVM, чтобы позволить этим объемам памяти с -Xms {memory} -Xmx {memory}
,
Я рекомендую вам проверить каталог, рассчитывающий дисковое пространство, и установить ограничение: 1 ГБ использовать файловую систему памяти, более 1 ГБ использовать файловую систему диска.
Еще одна вещь, проверьте параллелизм метода, вам не понравится более 1 потока, архивирующего 3Gb файлов