ZIP-архивы в языке Java

ZIP-архивы позволяют хранить один и более файлов в (обычно) сжатом формате. У каждого ZIP-архива имеется заголовок, содеражащий информацию вроде имени файла или использовавшегося для него метода сжатия. В Java для чтения ZIP-архивов применяется класс ZipInputStream. В каждом таком архиве всегда требуется просматривать отдельные записи(entries).

Метод getNextEntry возвращает описывающий запись объект типа ZipEntry. Метод read класс ZipInputStream изменяется так, чтобы он возвращал -1 в конце текущий записи(а не просто в конце ZIP-файла).

Далее вызывается метод closeEntry для получения возможности перехода к считыванию следующей записи. Ниже приведена типичная кодовая последовательность для выполнения считывания содержимого ZIP-файла:

ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
ZipEntry entry;
while((entry = zin.getNextEntry()) != null)
{
анализ entry;
считывание содержимого zin;
zin.closeEntry();
}
zin.close();

Для считывания содержимого конкретной записи из ZIP-файла эффективнее использовать не стандартный метод read, а методы какого-то обладающего большими находящегося внутри ZIP-архива, можно применить следующий цикл:

Scanner in = new Scanner(in);
while(in.hasNextLine())
выполнение каких-то операций с in.nextLine();

В случае возникновения ошибки при считывании ZIP-файла класс ZipInputStream выдает исключение ZipException. Обычно подобное происходит при повреждении ZIP-файла.

Для записи ZIP-файла применяется класс ZipOutputStream. Для каждой записи, которую требуется поместить в ZIP-файл, создается объект ZipEntry. Желаемое имя для файла передается конструктору ZipEntry, тот устанавливает остальные параметры, вроде даты создания файла и метода распаковки.

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

FileOutputStream fout = new FileOutputStream("test.zip");
ZipOutputStream zout = new ZipOutputStream(fout);
для всех файлов
{
ZipEntry ze = new ZipEntry(имя_файла);
zout.putNextEntry(ze);
отправка данных в поток zout;
zout.closeEntry();
}
zout.close();

JAR-файлы представляют собой те же ZIP-файлы, но только содержат записи несколько иного вида, называемые манифестом. Для считывания и записи манифестов применяются классы JarInputStream и JarOutputStream.

ZIP-потоки являются прекрасным примером мощи потоковой абстракции. При считывании данных, которые хранятся в сжатом виде, не нужно волноваться о том, будут ли они распаковаться по мере запрашивания. И источником байтов в ZIP-форматах вовсе не обязательно должен быть именно файл: ZIP-данные могут поступать и через сетевое подключение. На самом деле, при всяком считывании JAR-файла загрузчик классов аплета будет считывать и распаковывать данные именно из сети.

ZIP-архивы в языке JavaРис.2. Программа ZipTest

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

Вот полный код нашей программы ZipTest.java:

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.security.AccessControlContext;
import java.util.*;
import java.util.List;
import java.util.zip.*;
import javax.swing.*;
public class ZipTest {

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable()
		{
			public void run()
			{
				ZipTestFrame frame = new ZipTestFrame();
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.setVisible(true);
			}
		});
	}

}
class ZipTestFrame extends JFrame
{
	public ZipTestFrame()
	{
		setTitle("ZipTest");
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		// добавление меню и пунктов меню Open(Открыть) и Exit(Выход)
		JMenuBar menuBar = new JMenuBar();
		JMenu menu = new JMenu("File");
		JMenuItem openItem = new JMenuItem("Open");
		menu.add(openItem);
		openItem.addActionListener(new ActionListener() {
			
			public void actionPerformed(ActionEvent event) {
				JFileChooser chooser = new JFileChooser();
				chooser.setCurrentDirectory(new File("."));
				int r = chooser.showOpenDialog(ZipTestFrame.this);
				if(r == JFileChooser.APPROVE_OPTION)
				{
					zipname = chooser.getSelectedFile().getPath();
					fileCombo.removeAllItems();
					scanZipFile();
				}
			}
		});
		JMenuItem exitItem = new JMenuItem("Exit");
		menu.add(exitItem);
		exitItem.addActionListener(new ActionListener() {
			
			public void actionPerformed(ActionEvent event) {
				System.exit(0);
			}
		});
		menuBar.add(menu);
		setJMenuBar(menuBar);
		
		// Добавление текстовой обасти и комбинированного списка
		fileText = new JTextArea();
		fileCombo = new JComboBox();
		fileCombo.addActionListener(new ActionListener() {
			
			public void actionPerformed(ActionEvent e) {
				loadZipFile((String) fileCombo.getSelectedItem());
			}
		});
		add(fileCombo, BorderLayout.SOUTH);
		add(new JScrollPane(fileText), BorderLayout.CENTER);
	}
	
	/**
	 * Сканирование содержимого ZIP-архива и заполнение
	 * им комбинированного списка.
	 */
	
	public void scanZipFile()
	{
		new SwingWorker()
		{
			protected Void doInBackground() throws Exception {
				ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
				ZipEntry entry;
				while((entry = zin.getNextEntry()) != null)
				{
					publish(entry.getName());
					zin.closeEntry();
				}
				zin.close();
				return null;
			}
			protected void process(List names)
			{
				for(String name : names)
					fileCombo.addItem(name);
			}
		}.execute();
	}
	
	/**
	 * Загрузка файла из ZIP-архива в текстовую область
	 * @param name имя файла из архива
	 */
	
	public void loadZipFile(final String name)
	{
		fileCombo.setEnabled(false);
		fileText.setText("");
		new SwingWorker()
		{
			protected Void doInBackground() throws Exception
			{
				try
				{
					ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
					ZipEntry entry;
					
					// поиск записи с соответствующими именем в архиве
					while((entry = zin.getNextEntry()) != null)
					{
						if(entry.getName().equals(name))
						{
							// считывание записи в текстовую область
							Scanner in = new Scanner(zin);
							while(in.hasNextLine());
							{
								fileText.append(in.nextLine());
								fileText.append("\n");
							}
						}
						zin.closeEntry();
					}
					zin.close();
				}
				catch(IOException e)
				{
					e.printStackTrace();
				}
				return null;
			}
			protected void done()
			{
				fileCombo.setEnabled(true);
			}
		}.execute();
	}
 public static final int DEFAULT_WIDTH = 400;
 public static final int DEFAULT_HEIGHT = 300;
 private JComboBox fileCombo;
 private JTextArea fileText;
 private String zipname;
	
}