JavaRush /Курсы /JAVA 25 SELF /Архивы/сжатие: java.util.zip

Архивы/сжатие: java.util.zip

JAVA 25 SELF
41 уровень, 4 лекция
Открыта

1. Введение: зачем нужны архивы и сжатие в Java

В современном мире работа с архивами и сжатыми файлами — обычная задача: резервные копии, обмен файлами, логирование, хранение больших данных. Java предоставляет стандартные средства для работы с архивами формата ZIP и сжатыми файлами GZIP через пакет java.util.zip.

Что умеет Java:

  • Читать и создавать ZIP-архивы (многофайловые контейнеры).
  • Читать и создавать GZIP-файлы (сжатие одного файла).
  • Управлять содержимым архивов, фильтровать файлы по маске.
  • Контролировать уровень сжатия.
  • Проверять безопасность при распаковке (борьба с zip slip и zip bomb).

2. Основные классы

ZipInputStream и ZipOutputStream

Это потоковые классы для последовательного чтения/записи ZIP-архивов. Когда использовать: если нужно читать или создавать архив «на лету», без случайного доступа к отдельным файлам.

Пример: чтение архива

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

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        System.out.println("Файл: " + entry.getName());
        // Можно читать содержимое entry через zis.read(...)
        zis.closeEntry();
    }
}

Пример: создание архива

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

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    ZipEntry entry = new ZipEntry("hello.txt");
    zos.putNextEntry(entry);
    zos.write("Привет, архив!".getBytes());
    zos.closeEntry();
}

ZipFile

Класс для случайного доступа к содержимому ZIP-архива: можно быстро получить список файлов, открыть любой файл по имени, читать его содержимое.

Пример:

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

ZipFile zipFile = new ZipFile("archive.zip");
zipFile.stream().forEach(entry -> System.out.println(entry.getName()));

ZipEntry entry = zipFile.getEntry("hello.txt");
try (InputStream is = zipFile.getInputStream(entry)) {
    // Читаем содержимое файла
}
zipFile.close();

Когда использовать ZipFile?

  • Если нужно быстро получить список файлов, метаданные, размер, дату.
  • Если нужно читать отдельные файлы без последовательного перебора всего архива.

ZipEntry

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

import java.util.zip.ZipEntry;

ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("Описание файла");
entry.setTime(System.currentTimeMillis());

Уровни сжатия (Deflater)

При создании архива можно управлять степенью сжатия (от 0 — без сжатия, до 9 — максимальное сжатие):

import java.io.FileOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipOutputStream;

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    zos.setLevel(Deflater.BEST_COMPRESSION); // или 0..9
    // ...
}
  • Deflater.NO_COMPRESSION (0)
  • Deflater.BEST_SPEED (1)
  • Deflater.BEST_COMPRESSION (9)
  • Deflater.DEFAULT_COMPRESSION (-1)

Правило: чем выше уровень — тем медленнее, но сильнее сжимает.

3. Сжатие одного файла

GZIP — это формат для сжатия одного файла (не архива!). Применяется для логов, временных файлов, передачи по сети.

Пример: сжать файл

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

try (GZIPOutputStream gos = new GZIPOutputStream(new FileOutputStream("file.txt.gz"));
     FileInputStream fis = new FileInputStream("file.txt")) {
    fis.transferTo(gos);
}

Пример: распаковать файл

import java.util.zip.GZIPInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

try (GZIPInputStream gis = new GZIPInputStream(new FileInputStream("file.txt.gz"));
     FileOutputStream fos = new FileOutputStream("file.txt")) {
    gis.transferTo(fos);
}

Помните: GZIP работает только с одним файлом — он не сохраняет структуру папок и дополнительных метаданных. В отличие от него, ZIP позволяет упаковывать сразу несколько файлов и целые папки, сохраняя их структуру и информацию о каждом элементе.

4. Упаковка/распаковка директории, фильтры по PathMatcher

Упаковка директории в ZIP

Чтобы упаковать папку с файлами и подпапками, рекурсивно обходим дерево файлов и добавляем каждый файл в архив с правильным относительным путём (разделители в ZIP — всегда "/").

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Path sourceDir = Paths.get("myfolder");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    Files.walk(sourceDir)
        .filter(Files::isRegularFile)
        .forEach(path -> {
            String entryName = sourceDir.relativize(path).toString().replace("\\", "/");
            try (InputStream is = Files.newInputStream(path)) {
                zos.putNextEntry(new ZipEntry(entryName));
                is.transferTo(zos);
                zos.closeEntry();
            } catch (IOException e) { e.printStackTrace(); }
        });
}

Распаковка архива в директорию

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = Paths.get("output", entry.getName());
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Фильтрация файлов по маске (PathMatcher)

Можно фильтровать файлы для упаковки/распаковки по маске, например только "*.txt":

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.PathMatcher;

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.txt");
Files.walk(sourceDir)
    .filter(matcher::matches)
    .forEach(/* ... */);

5. Безопасность: Zip Slip, zip bomb, проверка нормализации пути

Zip Slip (атака через путь)

Проблема: Злоумышленник может создать архив с файлом, у которого имя — "../../../../etc/passwd". При распаковке без проверки такой файл может затереть системные файлы!

Решение: перед записью файла нормализуйте путь и убедитесь, что он не выходит за пределы целевой директории.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

Path targetDir = Paths.get("output").toAbsolutePath();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = targetDir.resolve(entry.getName()).normalize();
        if (!outPath.startsWith(targetDir)) {
            throw new IOException("Zip Slip: попытка записи вне целевой папки!");
        }
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Zip bomb (архив-бомба)

Проблема: архив может содержать файл, который после распаковки занимает гигабайты, хотя сам архив весит несколько килобайт. Это может «убить» сервер или диск.

Решение: ограничивайте максимальный размер распаковываемых файлов и общий объём распаковки, прерывая процесс при превышении лимита.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

long maxSize = 100 * 1024 * 1024; // 100 МБ
long totalUnzipped = 0;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = Paths.get("output", entry.getName()).normalize();
        Files.createDirectories(outPath.getParent());

        long written = 0;
        try (OutputStream os = Files.newOutputStream(outPath)) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = zis.read(buf)) > 0) {
                os.write(buf, 0, len);
                written += len;
                totalUnzipped += len;
                if (written > maxSize || totalUnzipped > maxSize) {
                    throw new IOException("Zip bomb detected!");
                }
            }
        }
        zis.closeEntry();
    }
}

6. Практика: CLI-утилита «zip/unzip» с масками

Напишем простую консольную утилиту для упаковки и распаковки файлов с поддержкой масок.

Пример: упаковка

// java ZipUtil zip myfolder archive.zip *.txt
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public static void zip(String sourceDir, String zipFile, String glob) throws IOException {
    Path src = Paths.get(sourceDir);
    PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
        Files.walk(src)
            .filter(Files::isRegularFile)
            .filter(matcher::matches)
            .forEach(path -> {
                String entryName = src.relativize(path).toString().replace("\\", "/");
                try (InputStream is = Files.newInputStream(path)) {
                    zos.putNextEntry(new ZipEntry(entryName));
                    is.transferTo(zos);
                    zos.closeEntry();
                } catch (IOException e) { e.printStackTrace(); }
            });
    }
}

Пример: распаковка с защитой от Zip Slip

// java ZipUtil unzip archive.zip output
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public static void unzip(String zipFile, String outDir) throws IOException {
    Path targetDir = Paths.get(outDir).toAbsolutePath();
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
            Path outPath = targetDir.resolve(entry.getName()).normalize();
            if (!outPath.startsWith(targetDir)) {
                throw new IOException("Zip Slip: попытка записи вне целевой папки!");
            }
            if (entry.isDirectory()) {
                Files.createDirectories(outPath);
            } else {
                Files.createDirectories(outPath.getParent());
                try (OutputStream os = Files.newOutputStream(outPath)) {
                    zis.transferTo(os);
                }
            }
            zis.closeEntry();
        }
    }
}

Пример запуска:

java ZipUtil zip myfolder archive.zip "*.txt"
java ZipUtil unzip archive.zip output

В первом примере команда java ZipUtil zip myfolder archive.zip "*.txt" упаковывает все .txt файлы из папки myfolder в архив archive.zip. Во втором примере java ZipUtil unzip archive.zip output распаковывает архив в папку output, при этом проверяется, чтобы ни один файл не был записан за пределы целевой директории — это и есть защита от Zip Slip.

1
Задача
JAVA 25 SELF, 41 уровень, 4 лекция
Недоступна
Отправка секретного доклада в космос: Операция "Сжатие" 🚀
Отправка секретного доклада в космос: Операция "Сжатие" 🚀
1
Задача
JAVA 25 SELF, 41 уровень, 4 лекция
Недоступна
Великое архивирование: Сбор текстовых свитков Цифровой Библиотеки 📚
Великое архивирование: Сбор текстовых свитков Цифровой Библиотеки 📚
1
Опрос
Оптимизация IO, 41 уровень, 4 лекция
Недоступен
Оптимизация IO
Оптимизация IO
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Gans Electro Уровень 59
10 сентября 2026
Мне понравилась лекция. Словил ностальгию по большой задаче «Архиватор», которая состояла из 20–30 подзадач, где мы писали вот такой архиватор. Кто бы что ни говорил, в старом курсе первые 20 уровней легендарны, а дальше «Большие задачи» и особенно «Игры» были лучшими практическими задачами.