Шаг 103.
Язык программирования Java.
Класс ZipOutputStream

На этом шаге мы рассмотрим класс ZipOutputStream

Для того чтобы заархивировать файл, или даже целую папку нужно сначала его создать. Для этого создают объект типа ZipOutputStream. У класса ZipOutputStream есть два конструктора:

ZipOutputStream(OutputStream out)
ZipOutputStream(OutputStream out, Charset charset)

Каждый из них первым аргументом принимает поток, в который мы будем архивировать файлы. Чаще всего в качестве потока используют объект класса FileOutputStream. Второй конструктор дополнительно вторым аргументом принимает кодировку, в которой будут сохранятся имена файлов в архив.

Перед началом архивирования можно указать уровень компрессии. Уровень компрессии — это число от 0 до 9. Задать уровень компресии можно с помощью функции setLevel объекта ZipOutputSream. По умолчанию используется уровень, равный следующему значению: Deflater.DEFAULT_COMPRESSION.

После того как установлен уровень компрессии можно приступать к самому архивированию. Для того чтобы добавить файл или папку в архив, нужно создать объект класса ZipEntry. Потом вызвать функцию putNextEntry, которой передается объект класса ZipEntry. Например, это можно сделать так:

ZipEntry ze = new ZipEntry(nameEntry);
out.putNextEntry(ze);

После вызова функции putNextEntry() будет создана запись в архиве. Но сам файл еще не будет заархивирован. Для этого нужно скопировать все содержимое этого файла в поток ZipOutputStream. Это можно сделать, написав следующую вспомогательную функцию:

void write(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    while (in.read(buffer) != -1) {
        out.write(buffer);
    }
}

Это классический прием копирования данных из входного потока в выходной поток.

Пусть у нас есть переменная file, которая ссылается на файл в папке. Тогда скопировать содержимое этого файла в архив можно следующим образом:

out.putNextEntry(new ZipEntry(file.getPath()));
try (InputStream in = new FileInputStream(file)) {
    write(in, out);
}
out.closeEntry();

Обратим внимание на вызов функции closeEntry(). Этот вызов необходим для корректного добавления записи в архив.

Заметим, что с помощью такого куска кода можно заархивировать либо файл, либо папку, но без ее содержимого. Для того чтобы заархивировать содержимое папки можно поступить следующим образом: создать объект класса File, который будет ссылаться на папку. Вызвать у этого объекта функцию listFiles() (напомним, эта функция возвращает список объектов File, ссылающиехся на содержимое папки). Потом циклом пройтись по всем этим объектам и каждый заархивировать. Если в исходной папке есть подпапки, то нужно вызвать функцию архивирования рекурсивно для подпапки.

При архивировании пустой папки есть одна особенность вызова функции putNextEntry(). Для того чтобы добавить в архив пункт, являющийся папкой, нужно к имени папки добавить в конце символ “/”.


Приведем ниже пример программы, которая архивирует файл или папку в указанный архив. Источник архивации и название архива передаются программы через аргументы командной строки.

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * Пример использования класса ZipOutputStream
 * */
public class Main {
    /*Объект класса File хранит ссылку на файл или папку, которую мы хотим 
                                                                   заархивировать*/
    private static File srcObject;

    /*Объект класса File хранит ссылку на zip файл, вкоторый мы хотим архивировать*/
    private static File destFile;

    /*Вспомогательная переменная для удаления из полного пути всех файлов при 
                                                                   архивировании*/
    private static String prefixObject;

    /**
     * Вспомогательная функция для копирования байтов из одного потока в другой
     * @param in поток ввода
     * @param out поток вывода
     * */
    private static void write(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        while (in.read(buffer) != -1) {
            out.write(buffer);
        }
    }

    /**
     * Функция для проверки аргументов командной строки
     * @param args аргументы командной строки
     * @return true, если мы корректно передали неообходимые аргументы, 
     *                                                     и false в противном случае
     * */
    private static boolean checkArgs(String[] args) {
        if (args.length < 2) {
            System.out.println("Введите путь к папке/файлу и путь к архиву");
            return false;
        }

        String srcName = args[0];

        srcObject = new File(srcName);
        if (!srcObject.exists()) {
            System.out.println("Источник для архивирования не существует на диске");
            return false;
        }

        String destName = args[1];
        destFile = new File(destName);
        File destParentDirectory = new File(destFile.getParent());
        if (!destParentDirectory.exists()) {
            System.out.println("Папка для сохранения архива не существует");
            return false;
        }

        return true;
    }

    /**
     * Вспомогательная функция проверки на пустоту директории
     * @param directory директория
     * @return true если папка пуста, и false в противном случае
     * */
    private static boolean isEmptyDirectory(File directory) {
        return directory.list().length == 0;
    }

    /**
     * Функция для добавления в архив файла
     * @param src файл, который хотим заархивировать
     * @param out поток для добавления в архив записи
     * @param entryName имя записи в архиве
     * */
    private static void toArchiveTheFile(File src, ZipOutputStream out, 
                                               String entryName) throws IOException {
        /*Добавляем новую запись в архиве*/
        out.putNextEntry(new ZipEntry(entryName));
        /*Копируем содержимое файла в поток архива*/
        try (InputStream in = new FileInputStream(src)) {
            write(in, out);
        }
        /*Закрываем запись в архиве*/
        out.closeEntry();
    }

    /**
     * Функция для архивирования папки со всей ее иерархией
     * @param src ссылка на директорию, которую хотим заархивировать
     * @param out поток для архивации
     * */
    private static void arhive(File src, ZipOutputStream out) throws IOException {
        /*Просматриваем каждый файл и папку в заданной папке*/
        for (File object : src.listFiles()) {
            /*Отбрасываем ненужные папки в пути для задания имени записи в архиве*/
            String entryName = object.getPath().substring(prefixObject.length() + 1);

            /*Если объект это папка*/
            if (object.isDirectory()) {
                /*либо она пустая либо нет*/
                if (isEmptyDirectory(object)) {
                    /*Если пустая, то нужно просто создать запись в архиве*/
                    System.out.print("name: " + entryName);
                    out.putNextEntry(new ZipEntry(entryName + "/"));
                    out.closeEntry();
                    System.out.println(" ------ OK");
                } else {
                  /*если папка не пустая, то мы вызываемся рекурсивно на этой папке*/
                    arhive(object, out);
                }
            } else {
                /*
                  Если рассматриваемый объект это файл, то нужно его заархивировать.
                  Для этого мы используем функцию toArchiveTheFile
                */
                System.out.print("name: " + entryName);
                toArchiveTheFile(object, out, entryName);
                System.out.println(" ------ OK");
            }
        }
    }

    public static void main(String[] args) throws IOException {
        /*Проверяем аргументы командной строки*/
        if (checkArgs(args)) {
            System.out.println("start arhiving...");
          
          /*отбрасываем все папки в пути до корня от нашего рассматриваемого объекта*/
            prefixObject = srcObject.getParent();

            /*Создаем поток для архивирования*/
            ZipOutputStream out = new ZipOutputStream(new FileOutputStream(destFile));
            /*Если мы захотели заархивироваться один файл*/
            if (srcObject.isFile()) {
                /*то просто его заархивируем*/
                String fileName = srcObject
                                      .getPath() 
                                      .substring(prefixObject.length() + 1);

                toArchiveTheFile(srcObject, out, fileName);
                System.out.println(" ------ OK");
            } else if (isEmptyDirectory(srcObject)) { 
                /*Если мы хотим заархивировать пустую папку*/
                /*То просто создадим запись в архиве*/
                out.putNextEntry(new ZipEntry(srcObject.getName() + "/"));
                out.closeEntry();
            } else {
                /*Если мы хотим заархивировать не пустую папку, 
                              то воспользуемся функцией arhive для архивации папки*/
                arhive(srcObject, out);
            }
            out.close();
            System.out.println("end arhiving.");
        }
    }
}

Проект можно взять здесь


Рис. 1. Вывод программы


На следующем шаге мы рассмотрим класс ZipInputStream

Предыдущий шаг Содержание Следующий шаг