We have had a lot of different options for creating Zip Files in .NET Applications for some time. .NET 4.5 has added built-in framework-based support for Zip files, which is a very valuable tool. The absence of built-in ZIP support meant there have been several useful libraries that provided that capability. In this post I will be comparing three such alternatives- DotNetZip, SharpZipLib, and the built-in .NET Framework 4.5 implementation.
In particular I am focusing on ease of use- how much code does it take to create a zip file? Libraries that provide these capabilities often fall into categories- libraries which make something possible, and libraries which make it easy.
First, we should look at the “testing framework” that will be used. The idea is straightforward- we want to simply have each one zip up a given folder to a given zip file, with different implementations. an Interface is the obvious choice:
1 2 3 4 |
public interface IZipTest { void ZipFolder(String sFolder, String sTargetZip); } |
A single-method interface. We’ll create an implementation for each of the selected libraries implementations, then test each one. Though performance is not the primary focus, we’ll go ahead and try to implement a basic average/total framework over 100 runs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace ZipTester { internal class Program { private static void Main(string[] args) { Dictionary<String, List<TimeSpan>> TimeIndex = new Dictionary<string, List<TimeSpan>>(); Dictionary<String, IZipTest> TestItems = new Dictionary<string, IZipTest>() { {"Framework", new Framework45Zip()}, {"DotNetZip", new DotNetZip()}, {"SharpZipLib", new SharpZipZip()} }; foreach(String keyadd in TestItems.Keys) { TimeIndex.Add(keyadd,new List<TimeSpan>()); } String sTestFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ZipTest"); String sZipFolder = "D:\\test"; //folder to zip up. if (!Directory.Exists(sTestFolder)) Directory.CreateDirectory(sTestFolder); for (int i = 0; i < 100; i++) { foreach (var loopzipitem in TestItems) { String sTargetFile = Path.Combine(sTestFolder, loopzipitem.Key + ".zip"); TimeSpan clock = RunZipTest(loopzipitem.Value, sZipFolder, sTargetFile); Console.WriteLine("Zipped using " + loopzipitem.Key + " to " + sTargetFile + " Elapsed:" + clock.ToString()); TimeIndex[loopzipitem.Key].Add(clock); } } Console.WriteLine("Summary:"); foreach(var iterate in TimeIndex) { TimeSpan sumvalue = new TimeSpan(Enumerable.Sum(from p in iterate.Value select p.Ticks)); TimeSpan avgvalue = new TimeSpan((int)(Enumerable.Average(from p in iterate.Value select p.Ticks))); Console.WriteLine(iterate.Key + ": Total:" + sumvalue.ToString() + ", Average:" + avgvalue.ToString()); } Console.ReadKey(); } private static TimeSpan RunZipTest(IZipTest instance, String sFolderPath, String sFileTarget) { if (!Directory.Exists(sFolderPath)) throw new DirectoryNotFoundException(sFolderPath); if (File.Exists(sFileTarget)) File.Delete(sFileTarget); Stopwatch sw = new Stopwatch(); sw.Start(); instance.ZipFolder(sFolderPath, sFileTarget); sw.Stop(); return sw.Elapsed; } } public interface IZipTest { void ZipFolder(String sFolder, String sTargetZip); } //the IZipTest implementations for each ZIP library type. } |
Nothing fancy- just as described above. A short helper will ensure the folder exists and the target zip does not when running one of the instances, simply so we don’t need to implement it in every single one.
DotNetZip
DotNetZip is the one I was most familiar with before this particular experiment, having used it in a few projects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System; using Ionic.Zip; namespace ZipTester { public class DotNetZip : IZipTest { public void ZipFolder(String sFolder, String sTargetZip) { using (ZipFile zf = new ZipFile(sTargetZip)) { zf.AddDirectory(sFolder); zf.Save(); } } } } |
Incredibly straightforward, overall- create a new Zip File, add the directory to it, and save the zipfile.
SharpZipLib
I don’t remember using SharpZipLib but I do remember discounting it over DotNetZip for some reason when I was originally looking for a ZIP tool. I was reacquainted with my reasoning in the process of writing the implementation here, which should be fairly obvious:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
using System; using System.IO; using ICSharpCode.SharpZipLib.Zip; namespace ZipTester { public class SharpZipZip : IZipTest { public void ZipFolder(String sFolder, String sTargetZip) { using (FileStream zipTargetStream = new FileStream(sTargetZip, FileMode.Create)) { using (ZipOutputStream zipStream = new ZipOutputStream(zipTargetStream)) { byte[] buff = new byte[1024*1024]; foreach (FileInfo fInfo in new DirectoryInfo(sFolder).GetFiles("*", SearchOption.AllDirectories)) { String cleanedFileName = fInfo.FullName; if (cleanedFileName.StartsWith(sFolder, StringComparison.OrdinalIgnoreCase)) cleanedFileName = cleanedFileName.Substring(sFolder.Length + 1); //String cleanedFileName = ZipEntry.CleanName(fInfo.FullName); using (FileStream fileStream = new FileStream(fInfo.FullName, FileMode.Open, FileAccess.Read)) { ZipEntry entry = new ZipEntry(cleanedFileName); entry.DateTime = fInfo.LastWriteTime; entry.Size = fileStream.Length; zipStream.PutNextEntry(entry); int sourcebytecount; while (true) { sourcebytecount = fileStream.Read(buff, 0, buff.Length); if (sourcebytecount == 0) break; zipStream.Write(buff, 0, sourcebytecount); } } } } } } } } |
This is just plain silly- this I suppose falls into the category of a library that makes something possible but doesn’t make a big effort to make it easy, or leaves out higher-level capabilities. Effectively when using SharpZipLib you’ll need to deal with the insertion of each ZipEntry yourself, or create your own wrappers for it. The wrappers are not super difficult to make but if you already have a zip task you want to write simply that is more overhead that needs to be considered.
Framework 4.5’s System.IO.Compression
The Framework implementation is as I mentioned a great addition to the framework. It’s usage is also surprisingly easy:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using System; using System.IO.Compression; namespace ZipTester { public class Framework45Zip : IZipTest { public void ZipFolder(String sFolder, String sTargetZip) { ZipFile.CreateFromDirectory(sFolder, sTargetZip); } } } |
If you include a reference to System.IO.Compression.FileSystems, you get access to some helper capabilities such as the ZipFile class, which I use here- the method is literally identical the interface method I defined myself.
I think the built-in .NET Version is the clear winner here, but DotNetZip is certainly a better alternative if using the 4.5 Framework has it’s other considerations. SharpZipLib is surprisingly unfluent and requires a lot of boilerplate to be used well, but it is an excellent low-level library for compression overall. Additionally, we have the performance considerations. After a single run, the program emitted the following results. I was testing with a reasonable set of files including a number of PNG images, text files, source code, and other data.
1 2 3 |
Framework: Total:00:01:24.6424069, Average:00:00:00.8464240 DotNetZip: Total:00:00:39.8107942, Average:00:00:00.3981079 SharpZipLib: Total:00:03:09.6732904, Average:00:00:01.8967329 |
DotNetZip comes in first, with the Framework lagging just behind. SharpZipLib executed a full second longer with each execution, which may be due to the lack of optimization within the program itself (Debug mode) since more of the processing was actually taking place within the code in the project. Unfortunately, this turned out to not be the case- setting the configuration to release did not significantly improve the performance of the SharpZipLib implementation, though it may simply be a less-than-optimal implementation. However if so, that does raise the further point that having to write your own boilerplate means you may not write the most effective and optimal boilerplate for a given task.
Have something to say about this post? Comment!