понедельник, 21 июня 2010 г.

Архивирование, разархивирование потоков, файлов и папок с использованием SharpZipLib .net & .net compact

Инструментарий: Visual Studio 2008 PRO, C#, SharpZipLib 0.85.5.0 и классы из пространства имен SharpZipLib.Zip

Содержание статьи:
1. работа с потоками
2. Работа с потоками, файлы и папки
3. Проще простого или FastZip class
4. Адаптируем под .net compact
5. Заключение

Исходники к статье

Данная статья поможет разобраться с API SharpZipLib, но раскрывает его только поверхностно и использует классы из пространства имен ICSharpCode.SharpZipLib.Zip. Более полные данные вы можете найти в документации на официальном сайте.

Работа с потоками.

Создадим новое консольное приложение с названием SharpZipLibAndStream. Добавим ссылку на библиотеку SharpZipLib и добавим в код:


using ICSharpCode.SharpZipLib.Zip;
using System.IO;


Теперь создадим эталонный поток, с которым в последствии сверим сжатый поток.


MemoryStream memoryStream = new MemoryStream();


Заполним его данными с помощью метода FillStream


FillStream(memoryStream,10);


Код метода FillStream:


private static void FillStream(Stream stream, int times)
{
string temp = "The quick brown fox jumps over the lazy dog";
string data = string.Empty;
for (int i = 0; i < times; i++) data += temp;
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(data);
stream.Write(buffer, 0, buffer.Length);
}


Как видно, из кода в поток помещается десять копий одной строки


Console.WriteLine(string.Format("Изначальная длинна потока: {0} bytes", memoryStream.Length));


Создадим экземпляр класса ZipOutputStream, в своем конструкторе он принимает единственный аргумент – поток, в который он будет сжимать данные.


MemoryStream zippedMemoryStream = new MemoryStream();
ZipOutputStream zipStream = new ZipOutputStream(zippedMemoryStream);


Теперь нам необходимо ( в противном случае мы получим InvalidOperationException:No Open Entry, при попытке записи данных в поток) добавить запись в оглавление архива, представленное классом ZipEntry, один из конструкторов которого принимает строку, как имя.


ZipEntry zipEntry = new ZipEntry("myData");
zipStream.PutNextEntry(zipEntry);


Поток готов к заполнению данными, заполним его аналогично memoryStream.


FillStream(zipStream,10);


После чего закроем ZipEntry


zipStream.CloseEntry();
Console.WriteLine(string.Format("Длинна потока в архиве: {0} bytes", zippedMemoryStream.Length));


Теперь можно сравнить длинну потоков – memoryStream 430 bytes и zippedMemoryStream 104.
Если мы изменим значение times в FillStream на 0, то получим значения 0 и 56,
56 bytes – столько занимает текущая запись в заголовке архива. Далее поставим значение 1 - выходные данные изменились на 43 и 100, 100 – 56 = 44, чуть больше самой строки (строка “The quick brown fox jumps over the lazy dog” включает в себя все буквы латиницы и довольно сложна для сжатия), что показывает малую эффективность при работе с данными небольшого размера. Изменим значение на 100 и получим 4300 и 125 как видно из результатов, ZipOutputStream прекрасно справляется с дублированием.

Разархивируем сжатый поток zippedMemoryStream.

Для извлечения данных из заархивированного потока используется класс ZipInputStream, конструктор которого принимает заархивированный поток.


ZipInputStream zipInputStream = new ZipInputStream(zippedMemoryStream);


Так как мы уже работали с zippedMemoryStream, переставим курсор в начало.


zippedMemoryStream.Position = 0;


После этого создадим в памяти новый поток для извлечения результатов.


MemoryStream fromZippedMemoryStream = new MemoryStream();


Выберем первую запись из оглавления архива и заполним поток разархивированными данными.


ZipEntry entry = zipInputStream.GetNextEntry();
byte[] outputBuffer = new byte[zipInputStream.Length];
zipInputStream.Read(outputBuffer, 0, outputBuffer.Length);
fromZippedMemoryStream.Write(outputBuffer, 0, outputBuffer.Length);
Console.WriteLine(string.Format("Длинна разархивированного потока: {0} bytes", fromZippedMemoryStream.Length));



Работа с потоками, файлы и папки.


В данной части мы заархивируем два текстовых файла с помощью ZipOutputStream, один из которых будет лежать в корне архива, а другой будет находиться внутри нескольких вложенных папок. После чего произведем распаковку архива на диск с помощью ZipInputStream.
Первоначально добавим в наш solution новое консольное приложение, с именем SharpZipLibStreamFilesAndFolders и сделаем его активным. Добавим ссылку на библиотеку SharpZipLib и добавим в код:


using ICSharpCode.SharpZipLib.Zip;
using System.IO;


Далее вызовем метод


CreateFilesAndDirectiries();


Код метода:


private static void CreateFilesAndDirectiries()
{
List files = new List();
files.Add(File.Create("file1.txt"));
files.Add(File.Create("file2.txt"));
files.ForEach(FillHelloWorldInStream);
files.ForEach(fStream => fStream.Close());
}

private static void FillHelloWorldInStream(FileStream file)
{
byte[] buffer = Encoding.UTF8.GetBytes("Hello World");
file.Write(buffer, 0, buffer.Length);
}


Как видно из кода – мы создаем в папке приложения два файла с именами file1.txt и file2.txt, потом записываем в каждый из файлов строку “Hello World” и закрываем потоки.


Займемся архивированием.


Далее мы создадим в папке с приложением новый файл my.zip, и передадим его FileStream поток в конструктор класса ZipOutputStream.


ZipOutputStream zipOutStream = new ZipOutputStream(File.Create("my.zip"));


После чего вызовем метод CreateFileZipEntry.


CreateFileZipEntry(zipOutStream, "file1.txt", "file1.txt");


Код метода:


private static void CreateFileZipEntry(ZipOutputStream zipOutStream, string name, string filePath)
{
ZipEntry fileZipEntry = new ZipEntry(name);
zipOutStream.PutNextEntry(fileZipEntry);
FileStream fileStram = File.Open(filePath, FileMode.Open);
byte[] buffer = new byte[fileStram.Length];
fileStram.Read(buffer, 0, buffer.Length);
zipOutStream.Write(buffer, 0, buffer.Length);
zipOutStream.CloseEntry();
}


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


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

Далее повторим вызов метода для второго файла, но изменим имя.


CreateFileZipEntry(zipOutStream, @"folder1\folder2\folder3\file2.txt", "file2.txt");


В результате этого вызова в архиве будет создана иерархия каталогов, в последнем из которых будет находиться file2.txt.

Все необходимые данные переданы в архив, после чего вызываем


zipOutStream.Close();



Теперь разархивируем только что созданный нами архив.


Создадим директорию в папке приложения, куда разархивируем данные.


Directory.CreateDirectory("ZipOutPut");


Создадим экземпляр класса ZipInputStream и передадим в его конструктор экземпляр FileStream, нашего архива.


ZipInputStream zipInputStream = new ZipInputStream(File.Open("my.zip", FileMode.Open));


Получим первую запись из заголовка архива.


ZipEntry zipEntryFromZippedFile = zipInputStream.GetNextEntry();


Разархивируем все в директорию ZipOutPut


while (zipEntryFromZippedFile != null)
{
if (zipEntryFromZippedFile.IsFile)
{
FileInfo fInfo = new FileInfo(string.Format("ZipOutPut\\{0}", zipEntryFromZippedFile.Name));
if (!fInfo.Directory.Exists) fInfo.Directory.Create();

FileStream file = fInfo.Create();
byte[] bufferFromZip = new byte[zipInputStream.Length];
zipInputStream.Read(bufferFromZip, 0, bufferFromZip.Length);
file.Write(bufferFromZip, 0, bufferFromZip.Length);
file.Close();
}
zipEntryFromZippedFile = zipInputStream.GetNextEntry();
}
zipInputStream.Close();


Мы по очереди перебираем все записи в заголовке архива и, если запись относится к файлу, проверяем, существует ли директория, к которой относится файл, при необходимости создавая оную. После создания файла, с обозначенным именем, читаем данные из архива и записываем их в FileStream созданного файла.

Проще простого или FastZip class

Если необходимо быстро создать архив или разархивировать архивный файл, то самый простой и быстрый способ использовать FastZip class.

Добавим в наш solution новое консольное приложение, с именем SharpZipLibFastZip, и сделаем его активным. Добавим ссылку на библиотеку SharpZipLib и добавим в код:


using ICSharpCode.SharpZipLib.Zip;
using System.IO;


Теперь вызовем метод CreateFilesAndDirectiries(), рассмотренный нами ранее, но с небольшими изменениями.


private static void CreateFilesAndDirectiries()
{
Directory.CreateDirectory("ToZip");
List files = new List();
files.Add(File.Create("ToZip\\file1.txt"));
files.Add(File.Create("ToZip\\file2.txt"));
files.ForEach(FillHelloWorldInStream);
files.ForEach(fStream => fStream.Close());
}


В папке приложения создается директория с названием “ToZip” и уже в ней будут находиться два текстовых файла.

Далее все гораздо проще, чем в предыдущих разделах.


FastZip zip = new FastZip();
zip.CreateZip("my.zip","ToZip", true,null);


Вот и все ;)

В папке приложения будет создан архив с именем my.zip и будет содержать в себе файлы из каталога ToZip.

Создается экземпляр класса FastZip, после чего вызывается один из вариантов метода CreateZip, в данном случае первый параметр – имя бушующего архива, второй – имя директории, которую нужно поместить в архив, далее булево значение рекурсивного поиска файлов, и fileFilter, основанный на регулярном выражении.

С разархивированием все так же просто:


zip.ExtractZip("my.zip", "New", null);


первый параметр – файл архива, второй – каталог, куда будет разархвивирован архив(даже если его не существует, как в данном случае, FatZip создаст его), третий – fileFilter.


Адаптируем под .net compact


Сделаем адаптацию на первом примере. Создадим новый compact проект Device Application, добавим ссылку на SharpZipLib библиотеку (из папки netcf-20) и заменим автоматически сгенерированный код на:


using System;
using System.Text;
using System.Windows.Forms;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;

namespace CompactZipStreams
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
MemoryStream memoryStream = new MemoryStream();
FillStream(memoryStream, 100);

MemoryStream zippedMemoryStream = new MemoryStream();
ZipOutputStream zipStream = new ZipOutputStream(zippedMemoryStream);
ZipEntry zipEntry = new ZipEntry("myData");
zipStream.PutNextEntry(zipEntry);
FillStream(zipStream, 100);
zipStream.CloseEntry();

ZipInputStream zipInputStream = new ZipInputStream(zippedMemoryStream);
zippedMemoryStream.Position = 0;
MemoryStream fromZippedMemoryStream = new MemoryStream();
ZipEntry entry = zipInputStream.GetNextEntry();
byte[] outputBuffer = new byte[zipInputStream.Length];
zipInputStream.Read(outputBuffer, 0, outputBuffer.Length);
fromZippedMemoryStream.Write(outputBuffer, 0, outputBuffer.Length);

MessageBox.Show(string.Format("Изначальная длинна: {0} bytes \n В архиве: {1} bytes \n Разархивированно в: {2} ", memoryStream.Length,zippedMemoryStream.Length,fromZippedMemoryStream.Length));


}
private static void FillStream(Stream stream, int times)
{
string temp = "The quick brown fox jumps over the lazy dog";
string data = string.Empty;
for (int i = 0; i < times; i++) data += temp;
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(data);
stream.Write(buffer, 0, buffer.Length);
}
}
}


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


Заключение

В данной статье была поверхностно рассмотрена работа с классами ZipOutputStream, ZipInputStream, ZipEntry и FastZip из популярной библиотеки SharpZipLib. Для более полного ознакомления используйте официальную документацию, вы сможете увеличить или уменьшить степень сжатия архива, задать пароль, добавить комментарии к архиву, тестировать и изменять существующие архивы. К тому же данная библиотека позволяет работать не только с zip архивами.

Александр Кобелев aka Megano