Работа с Zip-файлами в Java

Введение В этой статье я расскажу об основах создания, взаимодействия, проверки и извлечения файлов zip-архива с помощью Java (в частности, OpenJDK 11). Пример кода, используемый в этой статье, имеет форму проекта Gradle и размещен в этом репозитории GitHub [https://github.com/amcquistan/zip-files-in-java], чтобы вы могли запускать и экспериментировать. Будьте осторожны при изменении кода, удаляющего файлы. Как уже упоминалось, приведенные здесь примеры кода написаны с использованием Java 11 и util

Вступление

В этой статье я расскажу об основах создания, взаимодействия, проверки и извлечения файлов zip-архива с помощью Java (в частности, OpenJDK 11). Образец кода, используемый в этой статье, представляет собой проект Gradle и размещен в этом репозитории GitHub, чтобы вы могли запускать и экспериментировать. Будьте осторожны при изменении кода, удаляющего файлы.

Как уже упоминалось, приведенные здесь примеры кода написаны с использованием Java 11 и используют var которое было введено в Java 10, и парадигмы функционального программирования в Java 8, поэтому для их запуска требуется минимальная версия Java 10.

СОДЕРЖАНИЕ

Ключевые классы Java для работы с Zip-архивами

Я считаю, что неплохо начать с определения некоторых известных классов, которые обычно используются при работе с zip-архивами в Java. Эти классы java.nio.file пакетах java.util.zip или java.nio.file.

  • java.util.zip.ZipFile используется для чтения и взаимодействия с элементами ( ZipEntry ) в zip-архиве
  • java.util.zip.ZipEntry - это абстракция, представляющая такой элемент, как файл или каталог в zip-архиве (т. е. экземпляр ZipFile
  • java.util.zip.ZipOutputStream - это реализация абстрактного класса OutputStream, который используется для записи элементов в Zip-файл.
  • java.nio.file.Files - очень удобный класс утилит для потоковой передачи и копирования файловых данных в экземпляры ZipOutputStream или из экземпляров ZipFile.
  • java.nio.file.Path - еще один удобный класс утилит для эффективной работы с путями к файлам

Общие пути к файлам для примеров кода

В примере кода я использую два общих каталога для записи и чтения данных в / из которых оба относятся к корню проекта Gradle. Взгляните на связанный репо во введении или, что еще лучше, запустите образцы. Просто помните об этих двух переменных Path, поскольку они часто используются в качестве начального каталога для входов и выходов.

 public class App { 
 
 static final Path zippedDir = Path.of("ZippedData"); 
 static final Path inputDataDir = Path.of("InputData"); 
 
 // ... other stuff 
 } 

Проверка содержимого Zip-архива

Вы можете создать экземпляр ZipFile и передать ему путь к существующему zip-архиву, который, по сути, открывает его, как любой другой файл, а затем проверяет содержимое, запрашивая ZipEntry содержащееся внутри него. Обратите внимание на то, что ZipFile реализует интерфейс AutoCloseable , что делает его отличным кандидатом на конструкцию программирования Java «попытка с ресурсами», показанную ниже и во всех приведенных здесь примерах.

 static void showZipContents() { 
 try (var zf = new ZipFile("ZipToInspect.zip")) { 
 
 System.out.println(String.format("Inspecting contents of: %s\n", zf.getName())); 
 
 Enumeration<? extends ZipEntry> zipEntries = zf.entries(); 
 zipEntries.asIterator().forEachRemaining(entry -> { 
 System.out.println(String.format( 
 "Item: %s \nType: %s \nSize: %d\n", 
 entry.getName(), 
 entry.isDirectory() ? "directory" : "file", 
 entry.getSize() 
 )); 
 }); 
 } catch (IOException e) { 
 e.printStackTrace(); 
 } 
 } 

Запуск проекта Gradle с использованием следующего:

 $ ./gradlew run 

Это дает результат для метода App.showZipContents

 > Task :run 
 Inspecting contents of: ZipToInspect.zip 
 
 Item: ZipToInspect/ 
 Type: directory 
 Size: 0 
 
 Item: ZipToInspect/greetings.txt 
 Type: file 
 Size: 160 
 
 Item: ZipToInspect/InnerFolder/ 
 Type: directory 
 Size: 0 
 
 Item: ZipToInspect/InnerFolder/About.txt 
 Type: file 
 Size: 39 

Здесь вы можете видеть, что это распечатывает все файлы и каталоги в zip-архиве, даже файлы в каталогах.

Извлечение Zip-архива

Извлечение содержимого zip-архива на диск не требует ничего, кроме репликации той же структуры каталогов, что и внутри ZipFile , которую можно определить с помощью ZipEntry.isDirectory а затем копирования файлов, представленных в ZipEntry на диск.

 static void unzipAZip() { 
 var outputPath = Path.of("UnzippedContents"); 
 
 try (var zf = new ZipFile("ZipToInspect.zip")) { 
 
 // Delete if exists, then create a fresh empty directory to put the zip archive contents 
 initialize(outputPath); 
 
 Enumeration<? extends ZipEntry> zipEntries = zf.entries(); 
 zipEntries.asIterator().forEachRemaining(entry -> { 
 try { 
 if (entry.isDirectory()) { 
 var dirToCreate = outputPath.resolve(entry.getName()); 
 Files.createDirectories(dirToCreate); 
 } else { 
 var fileToCreate = outputPath.resolve(entry.getName()); 
 Files.copy(zf.getInputStream(entry), fileToCreate); 
 } 
 } catch(IOException ei) { 
 ei.printStackTrace(); 
 } 
 }); 
 } catch(IOException e) { 
 e.printStackTrace(); 
 } 
 } 

Запись файлов непосредственно в новый Zip-архив

Поскольку запись zip-архива на самом деле является не чем иным, как записью потока данных в какое-то место назначения (в данном случае Zip-файл), запись данных, таких как String, в zip-архив, отличается только тем, что вам нужно сопоставить данные, которые записано в ZipEntry добавленные в ZipOutputStream .

Опять же, ZipOutputStream реализует AutoCloseable , поэтому его лучше всего использовать с оператором try-with-resources. Единственная реальная проблема - не забыть закрыть свой ZipEntry когда вы закончите с каждым из них, чтобы было ясно, когда он больше не должен получать данные.

 static void zipSomeStrings() { 
 Map<String, String> stringsToZip = Map.ofEntries( 
 entry("file1", "This is the first file"), 
 entry("file2", "This is the second file"), 
 entry("file3", "This is the third file") 
 ); 
 var zipPath = zippedDir.resolve("ZipOfStringData.zip"); 
 try (var zos = new ZipOutputStream( 
 new BufferedOutputStream(Files.newOutputStream(zipPath)))) { 
 for (var entry : stringsToZip.entrySet()) { 
 zos.putNextEntry(new ZipEntry(entry.getKey())); 
 zos.write(entry.getValue().getBytes()); 
 zos.closeEntry(); 
 } 
 } catch (IOException e) { 
 e.printStackTrace(); 
 } 
 } 

Архивирование существующего файла в новый Zip-архив

Если вы ранее копировали файл на Java, то вы уже являетесь ПРОФЕССИОНАЛОМ в создании zip-архива из существующего файла (или каталога, если на то пошло). Опять же, единственное реальное отличие состоит в том, что вам нужно проявить дополнительную осторожность, чтобы убедиться, что вы сопоставляете файлы с соответствующими экземплярами ZipEntry

В этом примере я создаю входной файл «FileToZip.txt» и записываю в него данные «Привет, друзья Java!» а затем используйте Files.copy (Path, OutputStream), чтобы связать ZipEntry с файлом FileToZip.txt внутри zip-архива ZippedFile.zip, который я создаю с экземпляром ZipOutoutStream

 static void zipAFile() { 
 var inputPath = inputDataDir.resolve("FileToZip.txt"); 
 var zipPath = zippedDir.resolve("ZippedFile.zip"); 
 
 try (var zos = new ZipOutputStream( 
 new BufferedOutputStream(Files.newOutputStream(zipPath)))) { 
 
 Files.writeString(inputPath, "Howdy There Java Friends!\n"); 
 
 zos.putNextEntry(new ZipEntry(inputPath.toString())); 
 Files.copy(inputPath, zos); 
 zos.closeEntry(); 
 } catch (IOException e) { 
 e.printStackTrace(); 
 } 
 } 

Архивирование папки в новый ZIP-архив

Архивирование непустого каталога становится немного сложнее, особенно если вы хотите сохранить пустые каталоги в родительском каталоге. Чтобы поддерживать наличие пустого каталога в zip-архиве, вам необходимо обязательно создать запись с суффиксом разделителя каталогов файловой системы при создании ее ZipEntry , а затем немедленно закрыть ее.

В этом примере я создаю каталог с именем «foldertozip», содержащий структуру, показанную ниже, а затем заархивирую его в zip-архив.

 tree . 
 . 
 └── foldertozip 
 ├── emptydir 
 ├── file1.txt 
 └── file2.txt 

В следующем коде обратите внимание на то, что я использую метод Files.walk(Path) для обхода дерева каталогов «foldertozip» и поиска пустых каталогов («emptydir» в этом примере), и если / когда обнаружен, я присоединяю разделитель каталогов к имя в ZipEntry . После этого я закрываю его, как только добавляю в экземпляр ZipOutputStream

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

 static void zipADirectoryWithFiles() { 
 var foldertozip = inputDataDir.resolve("foldertozip"); 
 var dirFile1 = foldertozip.resolve("file1.txt"); 
 var dirFile2 = foldertozip.resolve("file2.txt"); 
 
 var zipPath = zippedDir.resolve("ZippedDirectory.zip"); 
 try (var zos = new ZipOutputStream( 
 new BufferedOutputStream(Files.newOutputStream(zipPath)))) { 
 
 Files.createDirectory(foldertozip); 
 Files.createDirectory(foldertozip.resolve("emptydir")); 
 Files.writeString(dirFile1, "Does this Java get you rev'd up or what?"); 
 Files.writeString(dirFile2, "Java Java Java ... Buz Buz Buz!"); 
 
 Files.walk(foldertozip).forEach(path -> { 
 try { 
 var reliativePath = inputDataDir.relativize(path); 
 var file = path.toFile(); 
 if (file.isDirectory()) { 
 var files = file.listFiles(); 
 if (files == null || files.length == 0) { 
 zos.putNextEntry(new ZipEntry( 
 reliativePath.toString() + File.separator)); 
 zos.closeEntry(); 
 } 
 } else { 
 zos.putNextEntry(new ZipEntry(reliativePath.toString())); 
 zos.write(Files.readAllBytes(path)); 
 zos.closeEntry(); 
 } 
 } catch(IOException e) { 
 e.printStackTrace(); 
 } 
 }); 
 } catch(IOException e) { 
 e.printStackTrace(); 
 } 
 } 

Заключение

В этой статье я обсудил и продемонстрировал современный подход к работе с zip-архивами на Java с использованием чистой Java и без сторонних библиотек. Вы также можете заметить, что я использую еще несколько современных функций языка Java, таких как парадигмы функционального программирования и var для предполагаемых переменных, поэтому при выполнении этих примеров убедитесь, что вы используете как минимум Java 10.

Как всегда, спасибо за чтение и не стесняйтесь комментировать или критиковать ниже.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus