Saturday, February 22, 2014

Используем DotNetZip для работы с ZIP архивами

В этой статье поговорим об еще одной популярной библиотеке для работы с архивами – DotNetZip. На данный момент это самая популярная библиотека для работы с архивами. О популярности этой библиотеки можно судить по количеству скачиваний. На момент написания статьи их было больше 172 тысяч, чем DotNetZip немного опережает своего ближайшего конкурента SharpZipLib
Для удобства работы с этой библиотекой создадим класс-обертку, которая будет выполнять за нас всю "грязную работу", а мы для этого ей передадим лишь несколько параметров. Основную работу мы возложим на созданный нами класс IonicZipHelper. Ниже приведена реализация этого класса.
public class IonicZipHelper
{
    public void CompressionDirectory(string fileName,
        string sourceDirectory,
        CompressionLevel compressionLevel = CompressionLevel.Default)
    {
        using (var zipFile = new ZipFile(fileName))
        {
            zipFile.CompressionLevel = compressionLevel;
            zipFile.AddDirectory(sourceDirectory, "\\");
            zipFile.Save();
        }
    }

    public void AppendFilesToZip(string fileName,
        List<string> appendFiles,
        CompressionLevel compressionLevel = CompressionLevel.Default)
    {
        using (var zipFile = ZipFile.Read(fileName))
        {
            zipFile.CompressionLevel = compressionLevel;
            zipFile.AddFiles(appendFiles, "\\");
            zipFile.Save();
        }
    }

    public void AppendFilesToZipFromArray(string fileName,
        List<byte[]> appendFiles,
        CompressionLevel compressionLevel = CompressionLevel.Default)
    {
        using (var zipFile = new ZipFile(fileName))
        {
            zipFile.CompressionLevel = compressionLevel;
            foreach (var file in appendFiles)
            {
                var tempFileName = Guid.NewGuid().ToString();
                zipFile.AddEntry(tempFileName, file);
            }
               
            zipFile.Save();
        }
    }

    public void ExtractZip(string fileName, string outFolder)
    {
        using (var zip = ZipFile.Read(fileName))
        {
            foreach (var e in zip)
            {
                e.Extract(outFolder, ExtractExistingFileAction.OverwriteSilently);
            }
        }
    }
}
Как видите, для написания класса-обертки над данной библиотекой нужно совсем мало кода. Для того, чтобы начать ее использовать, достаточно минимальных знаний языка C#. Мне не приходилось видеть библиотеки проще, чем данная. Перейдем к тестам данной библиотеки, чтобы посмотреть, как она справляется с поставленной задачей.
Класс для измерения тестов приведен ниже.
public static class Profiler
{
    public static double MeasureAction(Action action)
    {
        var st = new Stopwatch();
           
        st.Start();

        action();

        st.Stop();

        return st.Elapsed.TotalMilliseconds;
    }
}
Исходный код использования библиотеки Ionic.Zip:
static void Main(string[] args)
{
    //Compress directory
    var directory = CreateEmptyDirectory();
    var zipHelper = new IonicZipHelper();
    var resultFile = Path.Combine(Directory.GetCurrentDirectory(), "result.txt");
    if (File.Exists(resultFile))
        File.Delete(resultFile);

    var sourceDirecory = @"D:\CSharp\IocContainersDemo";
    //var sourceDirecory = @"D:\books\Introduction to F#";
    var size = GetDirectorySize(sourceDirecory);
    File.AppendAllText(resultFile, string.Format("Directory size {0} bytes {1}", size, Environment.NewLine));

    var ionicZlibPacking = "IonicZlibPacking.zip";

    string fileName = Path.Combine(directory, ionicZlibPacking);
    var result = Profiler.MeasureAction(() => zipHelper.CompressionDirectory(fileName,
                                                                                        sourceDirecory));
    File.AppendAllText(resultFile, string.Format("Zip directory with ZipFile {0} msec, Size: {1}{2}",
        result,
        new FileInfo(fileName).Length,
        Environment.NewLine));

    //Add all compression
    var compressFiles = new List<Tuple<string, Ionic.Zlib.CompressionLevel>>(new[]
        {
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test1.zip"), Ionic.Zlib.CompressionLevel.None),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test2.zip"), Ionic.Zlib.CompressionLevel.BestSpeed),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test3.zip"), Ionic.Zlib.CompressionLevel.Level2),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test4.zip"), Ionic.Zlib.CompressionLevel.Level3),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test5.zip"), Ionic.Zlib.CompressionLevel.Level4),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test6.zip"), Ionic.Zlib.CompressionLevel.Level5),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test7.zip"), Ionic.Zlib.CompressionLevel.Default),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test8.zip"), Ionic.Zlib.CompressionLevel.Level7),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test9.zip"), Ionic.Zlib.CompressionLevel.Level8),
            new Tuple<string, Ionic.Zlib.CompressionLevel>(Path.Combine(directory, "Test10.zip"), Ionic.Zlib.CompressionLevel.BestCompression),
        });
    foreach (var compressFile in compressFiles)
    {
        //Avoid closure variable
        var temp = compressFile;
        var res = Profiler.MeasureAction(() => zipHelper.CompressionDirectory(temp.Item1,
                                                                                        sourceDirecory,
                                                                                        temp.Item2));
        File.AppendAllText(resultFile, string.Format("Zip directory with ZipFile {0} msec, Size: {1}{2}",
            res,
            new FileInfo(temp.Item1).Length,
            Environment.NewLine));
    }

    //Add file to existing archive
    var tempFileOne = Path.Combine(directory, Guid.NewGuid().ToString());
    var tempFileTwo = Path.Combine(directory, Guid.NewGuid().ToString());
    File.AppendAllText(tempFileOne, "test1");
    File.AppendAllText(tempFileTwo, "test2");
    zipHelper.AppendFilesToZip(fileName,
        new List<string>(new [] { tempFileOne, tempFileTwo}));
           
    //Add byte[] arrays to archive
    var tempArchive = Path.Combine(directory, "TempArchive.zip");
    var array1 = Encoding.UTF8.GetBytes("Test1");
    var array2 = Encoding.UTF8.GetBytes("Test2");
    zipHelper.AppendFilesToZipFromArray(tempArchive,
        new List<byte[]>(new List<byte[]> { array1, array2}));

    //Read from archive
    var outputDirectory = Path.Combine(directory, "12345");
    if (!Directory.Exists(outputDirectory))
        Directory.CreateDirectory(outputDirectory);
    var outputResult = Profiler.MeasureAction(() => zipHelper.ExtractZip(fileName, outputDirectory));
    File.AppendAllText(resultFile, string.Format("Unzip file to directory {0} msec, FileName: {1}{2}",
        outputResult,
        fileName,
        Environment.NewLine));
           
    DeleteTempDirectory();
}


private static void DeleteTempDirectory()
{
    var directory = Path.Combine(Directory.GetCurrentDirectory(), "TestArchives");
    if (Directory.Exists(directory))
        Directory.Delete(directory, true);
}

private static string CreateEmptyDirectory()
{
    var directory = Path.Combine(Directory.GetCurrentDirectory(), "TestArchives");
    if (!Directory.Exists(directory))
        Directory.CreateDirectory(directory);
    else
    {
        var files = Directory.GetFiles(directory);
        foreach (var file in files)
        {
            File.Delete(file);
        }
    }
    return directory;
}

static long GetDirectorySize(string path)
{
    var a = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);

    return a.Select(name => new FileInfo(name)).Select(info => info.Length).Sum();
}
Интересный момент, что код для  упаковки и распаковки архива, добавления файлов в существующий архив, добавления байтовых данных в архив, получился больше, чем наш класс-враппер над данной библиотекой. Этот результат не может не радовать. J
Работу с архивом я условно разделил на 5 частей:
1) создание zip архива с сжатием по умолчанию;
2) создание zip-архивов с разной доступной степенью сжатия данных;
3) добавление файлов в уже существующий архив;
4) добавление байтовых данных в архив без промежуточного сохранения;
5) извлечение данных с архива.
Эти все пункты подписаны комментариями в коде и не нуждаются в дополнительном пояснении ввиду своей простоты.
Ниже приведен результат второго теста. Тест создан не искусственно, за основу взяты реальные данные. В тесте, приведенном выше, взят файл проекта с библиотеками. Тест, приведенный ниже, архивирует видеоуроки по языку F#.

Итоги
В этой статье мы рассмотрели использование библиотеки DotNetZip. Эта библиотека на данный момент является самой популярной для архивирования. Она проста в использовании и имеет широкие возможности. Она удобнее в использовании, по сравнению с другой популярной библиотекой SharpZipLib. Для того чтобы использовать DotNetZip, нужно написать меньше кода, чем для SharpZipLib. Попробуйте добавить файл в уже существующий архив, и Вы сразу поймете, что я имел ввиду под удобством. Надеюсь, использование этой библиотеки поможет Вам легко справляться с задачами архивирования.

No comments:

Post a Comment