Рубрики
Без рубрики

Сжатие и распаковка в Java

Узнайте, как архивировать и распаковывать файл, несколько файлов или полный каталог с помощью core Java.

Автор оригинала: baeldung.

1. Обзор

В этом кратком руководстве мы обсудим, как заархивировать файл в архив и как распаковать архив – все это с помощью основных библиотек, предоставляемых Java.

Эти основные библиотеки являются частью java.util.zip пакет – где мы можем найти все утилиты, связанные с архивированием и распаковкой.

2. Zip-файл

Давайте сначала рассмотрим простую операцию – сжатие одного файла.

Для нашего примера здесь мы заархивируем файл с именем test1.txt в архив с именем compressed.zip .

Конечно, сначала мы получим доступ к файлу с диска – давайте посмотрим:

public class ZipFile {
    public static void main(String[] args) throws IOException {
        String sourceFile = "test1.txt";
        FileOutputStream fos = new FileOutputStream("compressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        File fileToZip = new File(sourceFile);
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        zipOut.close();
        fis.close();
        fos.close();
    }
}

3. Zip Несколько Файлов

Далее давайте посмотрим, как заархивировать несколько файлов в один zip-файл. Мы будем сжимать test1.txt и test2.txt в multiCompressed.zip :

public class ZipMultipleFiles {
    public static void main(String[] args) throws IOException {
        List srcFiles = Arrays.asList("test1.txt", "test2.txt");
        FileOutputStream fos = new FileOutputStream("multiCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        for (String srcFile : srcFiles) {
            File fileToZip = new File(srcFile);
            FileInputStream fis = new FileInputStream(fileToZip);
            ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
            zipOut.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            fis.close();
        }
        zipOut.close();
        fos.close();
    }
}

4. Zip-каталог

Теперь давайте обсудим, как заархивировать весь каталог. Мы будем каталог zipTest в dirCompressed.zip :

public class ZipDirectory {
    public static void main(String[] args) throws IOException {
        String sourceFile = "zipTest";
        FileOutputStream fos = new FileOutputStream("dirCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        File fileToZip = new File(sourceFile);

        zipFile(fileToZip, fileToZip.getName(), zipOut);
        zipOut.close();
        fos.close();
    }

    private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
        if (fileToZip.isHidden()) {
            return;
        }
        if (fileToZip.isDirectory()) {
            if (fileName.endsWith("/")) {
                zipOut.putNextEntry(new ZipEntry(fileName));
                zipOut.closeEntry();
            } else {
                zipOut.putNextEntry(new ZipEntry(fileName + "/"));
                zipOut.closeEntry();
            }
            File[] children = fileToZip.listFiles();
            for (File childFile : children) {
                zipFile(childFile, fileName + "/" + childFile.getName(), zipOut);
            }
            return;
        }
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileName);
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        fis.close();
    }
}

Обратите внимание, что:

  • Чтобы заархивировать подкаталоги, мы рекурсивно перебираем их.
  • Каждый раз, когда мы находим каталог, мы добавляем его имя к имени потомков ZipEntry , чтобы сохранить иерархию.
  • Мы также создаем запись в каталоге для каждого пустого каталога

5. Распакуйте архив

Теперь давайте распакуем архив и извлекем его содержимое.

Для этого примера мы распакуем compressed.zip в новую папку с именем unzipTest .

Давайте посмотрим:

public class UnzipFile {
    public static void main(String[] args) throws IOException {
        String fileZip = "src/main/resources/unzipTest/compressed.zip";
        File destDir = new File("src/main/resources/unzipTest");
        byte[] buffer = new byte[1024];
        ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip));
        ZipEntry zipEntry = zis.getNextEntry();
        while (zipEntry != null) {
           // ...
        }
        zis.closeEntry();
        zis.close();
    }
}

Внутри цикла while , мы переберем каждый ZipEntry и сначала проверим, является ли это каталогом . Если это так, то мы создадим каталог с помощью метода mkdirs () ; в противном случае мы продолжим создание файла:

while (zipEntry != null) {
     File newFile = newFile(destDir, zipEntry);
     if (zipEntry.isDirectory()) {
         if (!newFile.isDirectory() && !newFile.mkdirs()) {
             throw new IOException("Failed to create directory " + newFile);
         }
     } else {
         // fix for Windows-created archives
         File parent = newFile.getParentFile();
         if (!parent.isDirectory() && !parent.mkdirs()) {
             throw new IOException("Failed to create directory " + parent);
         }
         
         // write file content
         FileOutputStream fos = new FileOutputStream(newFile);
         int len;
         while ((len = zis.read(buffer)) > 0) {
             fos.write(buffer, 0, len);
         }
         fos.close();
     }
 zipEntry = zis.getNextEntry();
}

Здесь следует отметить, что в ветке else мы также сначала проверяем, существует ли родительский каталог файла. Это необходимо для архивов, созданных в Windows, где корневые каталоги не имеют соответствующей записи в zip-файле.

Еще один ключевой момент можно увидеть в методе newFile() :

public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
    File destFile = new File(destinationDir, zipEntry.getName());

    String destDirPath = destinationDir.getCanonicalPath();
    String destFilePath = destFile.getCanonicalPath();

    if (!destFilePath.startsWith(destDirPath + File.separator)) {
        throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
    }

    return destFile;
}

Этот метод защищает от записи файлов в файловую систему за пределами целевой папки. Эта уязвимость называется Zip Slip, и вы можете подробнее об этом прочитать здесь .

6. Заключение

В этом руководстве показано, как мы можем использовать библиотеки Java для операций архивирования и распаковки файлов.

Реализацию этих примеров можно найти на GitHub .