На этом шаге мы рассмотрим класс 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