ここでは、ZipFileクラスやZipArchiveクラスを使用して、ZIP書庫(アーカイブ)を作成したり、展開(解凍)したり、中身を見たりする方法について説明します。残念ながら、これらのクラスは.NET Framework 4.5以降でしか使用できません。
なお、.NET FrameworkでZIP書庫を扱う方法は、これ以外にも、以下の記事で説明しているような方法もあります。
ZipFile.CreateFromDirectoryメソッドを使用すると、簡単にZIP書庫を作成することができます。このメソッドでは、指定したディレクトリ以下のすべてのファイルとディレクトリをZIP書庫に格納します。
このZipFileクラスを使用するには、参照設定に「System.IO.Compression.FileSystem.dll」を追加する必要があります。方法が分からないという場合は、「「○○○.dllを参照に追加します」の意味は?」をご覧ください。
次の例では、ディレクトリ「C:\test\dir」以下を圧縮して「C:\test\1.zip」という書庫を作成しています。
'ZIP書庫を作成 System.IO.Compression.ZipFile.CreateFromDirectory( _ "C:\test\dir", _ "C:\test\1.zip", _ System.IO.Compression.CompressionLevel.Optimal, _ False, _ System.Text.Encoding.GetEncoding("shift_jis"))
//ZIP書庫を作成 System.IO.Compression.ZipFile.CreateFromDirectory( @"C:\test\dir", @"C:\test\1.zip", System.IO.Compression.CompressionLevel.Optimal, false, System.Text.Encoding.GetEncoding("shift_jis"));
作成するZIP書庫が既に存在している場合は、例外IOExceptionが発生します。
3番目の引数には、CompressionLevel列挙体の値を指定します。Optimalは圧縮率が良いですが、圧縮に時間がかかります。逆にFastestは速いですが、圧縮率は悪いです。NoCompressionは圧縮しません。
上記の例で4番目の引数をTrueにすると、書庫のルートにディレクトリ「dir」が作成され、そこにすべてのファイルとディレクトリが置かれます。Falseの場合は、ディレクトリ「dir」は作成されません。ディレクトリ構造は、どちらにしても、維持されて格納されます。
5番目の引数には、ファイル名に使用するエンコードを指定します。上記の例では「Shift JIS」にしています。最近のアーカイバならUTF-8に対応していると思いますので、ここはEncoding.UTF8か、もしくは省略してもよいでしょう。
ZipFile.CreateFromDirectoryメソッドにはパラメータが2つのオーバーロードと、4つのオーバーロードもあります。3番目の引数を省略するとOptimalに、4番目の引数を省略するとFalseになります。5番目の引数を省略した場合は、ファイル名がASCII文字だけの場合はANSI(Encoding.GetEncoding(0))、それ以外はUTF-8になるようです(ILSpyで確認)。
補足:書庫に格納される時、元のファイルの属性は無視されるようです。
ZIP書庫を展開するには、ZipFile.ExtractToDirectoryメソッドを使用します。このメソッドを使用すると、指定したディレクトリに、書庫内のファイルとディレクトリをすべて展開します。
'ZIP書庫を展開する
System.IO.Compression.ZipFile.ExtractToDirectory( _
"C:\test\1.zip", _
"C:\test\extra", _
System.Text.Encoding.GetEncoding("shift_jis"))
//ZIP書庫を展開する
System.IO.Compression.ZipFile.ExtractToDirectory(
@"C:\test\1.zip",
@"C:\test\extra",
System.Text.Encoding.GetEncoding("shift_jis"));
展開先に指定したディレクトリが存在しない場合は、作成されます。展開先にすでにファイルが存在している場合は、例外IOExceptionが発生します(ディレクトリが存在している場合は、発生しません)。
3番目の引数は、省略できます。省略した場合は、ANSI(Encoding.GetEncoding(0))になるようです。ただし、Encodingを指定しようがしまいが、ZIP書庫のヘッダにUTF-8を使用しているフラグが立っている場合は、UTF-8になるようです。
補足:展開する時、書庫内のファイルの属性は無視されるようです。
ZIP書庫内のファイルとディレクトリを列挙するには、ZipFile.OpenReadメソッドでZIP書庫を開き、ZipArchive.Entriesプロパティを使用します。
次の例では、「C:\test\1.zip」に格納されているファイルとディレクトリの情報を列挙しています。ZipArchiveクラスを使用するには、参照設定に「System.IO.Compression.dll」を追加する必要がありますので、以下のコードを実行するには、「System.IO.Compression.FileSystem.dll」と「System.IO.Compression.dll」の両方が参照設定に追加されている必要があります。
'Imports System.IO.Compression 'ZIP書庫を開く Using a As ZipArchive = ZipFile.OpenRead("C:\test\1.zip") 'Encodingを指定する場合は、次のようにする 'Using a As ZipArchive = ZipFile.Open("C:\test\1.zip", _ ' ZipArchiveMode.Read, _ ' System.Text.Encoding.GetEncoding("shift_jis")) '書庫内のファイルとディレクトリを列挙する For Each e As ZipArchiveEntry In a.Entries Console.WriteLine("名前 : {0}", e.Name) 'ディレクトリ付きのファイル名 Console.WriteLine("フルパス : {0}", e.FullName) Console.WriteLine("サイズ : {0}", e.Length) Console.WriteLine("圧縮サイズ : {0}", e.CompressedLength) Console.WriteLine("更新日時 : {0}", e.LastWriteTime) Next End Using
//using System.IO.Compression; //ZIP書庫を開く using (ZipArchive a = ZipFile.OpenRead(@"C:\test\1.zip")) //Encodingを指定する場合は、次のようにする //using (ZipArchive a = ZipFile.Open(@"C:\test\1.zip", // ZipArchiveMode.Read, // System.Text.Encoding.GetEncoding("shift_jis"))) { //書庫内のファイルとディレクトリを列挙する foreach (ZipArchiveEntry e in a.Entries) { Console.WriteLine("名前 : {0}", e.Name); //ディレクトリ付きのファイル名 Console.WriteLine("フルパス : {0}", e.FullName); Console.WriteLine("サイズ : {0}", e.Length); Console.WriteLine("圧縮サイズ : {0}", e.CompressedLength); Console.WriteLine("更新日時 : {0}", e.LastWriteTime); } }
ZipFile.OpenReadメソッドでは、Encodingを指定することができません。Encodingの判断は、ZipFile.ExtractToDirectoryメソッドと同様、ヘッダにUTF-8のフラグが立っていればUTF-8、そうでなければANSIになるようです。Encodingを指定して開きたい場合は、上記コードのコメントにあるように、ZipFile.OpenReadメソッドの代わりにZipFile.Openメソッドを使います。
ディレクトリの場合、ZipArchiveEntry.Nameプロパティは空の文字列になるようです。また、FullNameプロパティの末尾には、「/」が付くようです。
ZIP書庫内のファイルを1つだけ展開するには、ZipArchive.GetEntryメソッドで展開したいファイルのZipArchiveEntryを取得して、ZipArchiveEntry.ExtractToFileメソッドを実行します。GetEntryメソッドには探したいファイルをフルパス(ディレクトリを含むパス)で指定し、見つからなかった場合はnullが返ります。
'Imports System.IO.Compression 'ZIP書庫を開く Using a As ZipArchive = ZipFile.OpenRead("C:\test\1.zip") '「dir/1.txt」のZipArchiveEntryを取得する Dim e As ZipArchiveEntry = a.GetEntry("dir/1.txt") If e Is Nothing Then '見つからなかった時 Console.WriteLine("dir/1.txt が見つかりませんでした。") Else '見つかった時は「C:\test\dir\1.txt」として展開する '2番目の引数をTrueにすると、上書きする e.ExtractToFile("C:\test\dir\1.txt", True) End If End Using
//using System.IO.Compression; //ZIP書庫を開く using (ZipArchive a = ZipFile.OpenRead(@"C:\test\1.zip")) { //「dir/1.txt」のZipArchiveEntryを取得する ZipArchiveEntry e = a.GetEntry("dir/1.txt"); if (e == null) { //見つからなかった時 Console.WriteLine("dir/1.txt が見つかりませんでした。"); } else { //見つかった時は「C:\test\dir\1.txt」として展開する //2番目の引数をTrueにすると、上書きする e.ExtractToFile(@"C:\test\dir\1.txt", true); } }
補足:ディレクトリを展開しようとすると、サイズ0のファイルが作成されるようです。
ZipArchiveEntry.Deleteメソッドを使うと、そのファイルを削除することができます。
Deleteメソッドを使用すること以外は先ほどとほぼ同じですが、ZipFile.OpenReadメソッドでZIP書庫を開くと、ZIP書庫の更新ができませんので、ZipFile.Openメソッドで開きます。
'Imports System.IO.Compression '読み取りと書き込みができるようにして、ZIP書庫を開く Using a As ZipArchive = ZipFile.Open("C:\test\1.zip", ZipArchiveMode.Update) '「1.txt」のZipArchiveEntryを取得する Dim e As ZipArchiveEntry = a.GetEntry("1.txt") If e Is Nothing Then '見つからなかった時 Console.WriteLine("1.txt が見つかりませんでした。") Else '見つかった時は削除する e.Delete() End If End Using
//using System.IO.Compression; //読み取りと書き込みができるようにして、ZIP書庫を開く using (ZipArchive a = ZipFile.Open(@"C:\test\1.zip", ZipArchiveMode.Update)) { //「1.txt」のZipArchiveEntryを取得する ZipArchiveEntry e = a.GetEntry(@"1.txt"); if (e == null) { //見つからなかった時 Console.WriteLine("1.txt が見つかりませんでした。"); } else { //見つかった時は削除する e.Delete(); } }
補足:空でないディレクトリも削除できるようですが、ディレクトリだけが削除され、ファイルは残るため、しない方がよさそうです。
ZipArchiveEntry.Openメソッドを使うと、ZIP書庫内のファイルを開いて、Streamを取得することができます。
以下の例では、ZIP書庫内のファイル「1.txt」をShift JISとして開き、その内容を取得しています。
'Imports System.IO 'Imports System.IO.Compression 'ZIP書庫を開く Using a As ZipArchive = ZipFile.OpenRead("C:\test\1.zip") '「1.txt」のZipArchiveEntryを取得する Dim e As ZipArchiveEntry = a.GetEntry("1.txt") If e Is Nothing Then '見つからなかった時 Console.WriteLine("1.txt が見つかりませんでした。") Else '見つかった時は開く Using sr As New StreamReader(e.Open(), _ System.Text.Encoding.GetEncoding("shift_jis")) 'すべて読み込む Dim s As String = sr.ReadToEnd() Console.Write(s) End Using End If End Using
//using System.IO; //using System.IO.Compression; //ZIP書庫を開く using (ZipArchive a = ZipFile.OpenRead(@"C:\test\1.zip")) { //「1.txt」のZipArchiveEntryを取得する ZipArchiveEntry e = a.GetEntry(@"1.txt"); if (e == null) { //見つからなかった時 Console.WriteLine("1.txt が見つかりませんでした。"); } else { //見つかった時は開く using (StreamReader sr = new StreamReader(e.Open(), System.Text.Encoding.GetEncoding("shift_jis"))) { //すべて読み込む string s = sr.ReadToEnd(); Console.Write(s); } } }
ZIP書庫にファイルを追加するには、ZipArchive.CreateEntryFromFileメソッドを使います。
次の例では、ファイル「C:\test\1.txt」を「1.txt」としてZIP書庫に追加しています。
'Imports System.IO.Compression '読み取りと書き込みができるようにして、ZIP書庫を開く Using a As ZipArchive = ZipFile.Open("C:\test\1.zip", ZipArchiveMode.Update) 'ファイル「C:\test\1.txt」を「1.txt」としてZIPに追加する Dim e As ZipArchiveEntry = a.CreateEntryFromFile("C:\test\1.txt", "1.txt") End Using
//using System.IO.Compression; //読み取りと書き込みができるようにして、ZIP書庫を開く using (ZipArchive a = ZipFile.Open(@"C:\test\1.zip", ZipArchiveMode.Update)) { //ファイル「C:\test\1.txt」を「1.txt」としてZIPに追加する ZipArchiveEntry e = a.CreateEntryFromFile(@"C:\test\1.txt", @"1.txt"); }
ZipArchive.CreateEntryメソッドを使えば、追加するファイルが存在しなくても、その内容をプログラムで書き込むことができます。
以下の例では、ZIP書庫に「1.txt」というファイルを追加して、そこに文字列を書き込んでいます。
'Imports System.IO 'Imports System.IO.Compression '読み取りと書き込みができるようにして、ZIP書庫を開く Using a As ZipArchive = ZipFile.Open("C:\test\1.zip", ZipArchiveMode.Update) 'ファイル「1.txt」を追加する Dim e As ZipArchiveEntry = a.CreateEntry("1.txt") '書き込むために、開く Using sw As New StreamWriter(e.Open(), _ System.Text.Encoding.GetEncoding("shift_jis")) '書き込む sw.Write("こんにちは。") End Using End Using
//using System.IO; //using System.IO.Compression; //読み取りと書き込みができるようにして、ZIP書庫を開く using (ZipArchive a = ZipFile.Open(@"C:\test\1.zip", ZipArchiveMode.Update)) { //ファイル「1.txt」を追加する ZipArchiveEntry e = a.CreateEntry("1.txt"); //書き込むために、開く using (StreamWriter sw = new StreamWriter(e.Open(), System.Text.Encoding.GetEncoding("shift_jis"))) { //書き込む sw.Write("こんにちは。"); } }
なお、空のディレクトリを追加する場合は、名前の末尾に「/」を付けて、ZipArchive.CreateEntryメソッドを呼び出せばよいようです。
'Imports System.IO.Compression '読み取りと書き込みができるようにして、ZIP書庫を開く Using a As ZipArchive = ZipFile.Open("C:\test\1.zip", ZipArchiveMode.Update) '空のディレクトリ「dir」を追加する Dim e As ZipArchiveEntry = a.CreateEntry("dir/") End Using
//using System.IO.Compression; //読み取りと書き込みができるようにして、ZIP書庫を開く using (ZipArchive a = ZipFile.Open(@"C:\test\1.zip", ZipArchiveMode.Update)) { //空のディレクトリ「dir」を追加する ZipArchiveEntry e = a.CreateEntry("dir/"); }
補足:例えば、「dir」というディレクトリに「1.txt」というファイルを追加する場合は、ファイル名を「dir/1.txt」とします。しかし、「dir」というディレクトリが存在していない時に「dir/1.txt」を追加しても、「dir」というディレクトリが自動的に作成される訳ではありません。よって、その場合は、「dir」というディレクトリを作成してから「dir/1.txt」を追加した方がよいでしょう。
Windows 8.x ストアアプリではSystem.IO.Compression.FileSystem.dllが使えませんが、System.IO.Compression.dllは使えます。System.IO.Compression.FileSystem.dllが使えない場合は、ZipFileクラスとZipFileExtensionsクラスが使えません。ZipFileExtensionsはZipArchiveとZipArchiveEntryの拡張メソッドを提供するクラスで、ここで紹介したメソッドでは、ZipArchive.CreateEntryFromFileとZipArchiveEntry.ExtractToFileメソッドが該当します。つまり、ZipFileクラスとこれらのメソッドを使わないのであれば、System.IO.Compression.FileSystem.dllを使う必要はありません。
ZipFile.OpenReadとOpenメソッドは、File.Openメソッドで代用することができます。例えば、ZIP書庫内のファイルを列挙する例は、次のようになります。
'Imports System.IO 'Imports System.IO.Compression 'ZIP書庫を開く Using fs As FileStream = _ File.Open("C:\test\1.zip", FileMode.Open, FileAccess.Read) 'ZipArchiveを作成する Using a As New ZipArchive(fs, ZipArchiveMode.Read, False, _ System.Text.Encoding.GetEncoding("shift_jis")) '書庫内のファイルとディレクトリを列挙する For Each e As ZipArchiveEntry In a.Entries Console.WriteLine(e.FullName) Next End Using End Using
//using System.IO; //using System.IO.Compression; //ZIP書庫を開く using (FileStream fs = File.Open(@"C:\test\1.zip", FileMode.Open, FileAccess.Read)) { //ZipArchiveを作成する using (ZipArchive a = new ZipArchive(fs, ZipArchiveMode.Read, false, System.Text.Encoding.GetEncoding("shift_jis"))) { //書庫内のファイルとディレクトリを列挙する foreach (ZipArchiveEntry e in a.Entries) { Console.WriteLine(e.FullName); } } }
ZipFile.CreateFromDirectoryメソッドとZipFile.ExtractToDirectoryメソッドの代わりは、ちょっと面倒です。
まずは、指定したディレクトリ以下をZIP書庫に格納する簡単な例を紹介します。
'Imports System.IO 'Imports System.IO.Compression Dim zipFilePath As String = "C:\test\1.zip" Dim directoryPath As String = "C:\test\dir\" 'ZIPファイルを開く Using fs As FileStream = _ File.Open(zipFilePath, FileMode.CreateNew, FileAccess.Write) 'ZipArchiveを作成する Using a As New ZipArchive(fs, ZipArchiveMode.Create) 'ディレクトリ以下のファイルとディレクトリを列挙する For Each f As String In Directory.EnumerateFileSystemEntries( _ directoryPath, "*", SearchOption.AllDirectories) 'ZIPに追加する時の名前を決める Dim entryName As String = f.Substring(directoryPath.Length) If File.Exists(f) Then 'ファイルの時 'ファイルを開く Using fs2 As FileStream = _ File.Open(f, FileMode.Open, FileAccess.Read) 'ZipArchiveEntryを作成する Dim e As ZipArchiveEntry = a.CreateEntry(entryName) '更新日時を設定する。開く前に設定する必要あり。 e.LastWriteTime = File.GetLastWriteTime(f) 'エントリを開く Using strm As Stream = e.Open() 'ファイルの内容をコピーする fs2.CopyTo(strm) End Using End Using ElseIf Directory.Exists(f) Then 'ディレクトリの時 Dim e As ZipArchiveEntry = a.CreateEntry(entryName & "/") '更新日時を設定する e.LastWriteTime = File.GetLastWriteTime(f) End If Next End Using End Using
//using System.IO; //using System.IO.Compression; string zipFilePath = @"C:\test\1.zip"; string directoryPath = @"C:\test\dir\"; //ZIPファイルを開く using (FileStream fs = File.Open(zipFilePath, FileMode.CreateNew, FileAccess.Write)) { //ZipArchiveを作成する using (ZipArchive a = new ZipArchive(fs, ZipArchiveMode.Create)) { //ディレクトリ以下のファイルとディレクトリを列挙する foreach (string f in Directory.EnumerateFileSystemEntries( directoryPath, "*", SearchOption.AllDirectories)) { //ZIPに追加する時の名前を決める string entryName = f.Substring(directoryPath.Length); if (File.Exists(f)) { //ファイルの時 //ファイルを開く using (FileStream fs2 = File.Open(f, FileMode.Open, FileAccess.Read)) { //ZipArchiveEntryを作成する ZipArchiveEntry e = a.CreateEntry(entryName); //更新日時を設定する。開く前に設定する必要あり。 e.LastWriteTime = File.GetLastWriteTime(f); //エントリを開く using (Stream strm = e.Open()) { //ファイルの内容をコピーする fs2.CopyTo(strm); } } } else if (Directory.Exists(f)) { //ディレクトリの時 ZipArchiveEntry e = a.CreateEntry(entryName + "/"); //更新日時を設定する e.LastWriteTime = File.GetLastWriteTime(f); } } } }
次に、ZIP書庫を指定したディレクトリに展開する簡単な例を示します。
'Imports System.IO 'Imports System.IO.Compression Dim zipFilePath As String = "C:\test\1.zip" Dim directoryPath As String = "C:\test\extra\" 'ZIPファイルを開く Using fs As FileStream = _ File.Open(zipFilePath, FileMode.Open, FileAccess.Read) 'ZipArchiveを作成する Using a As New ZipArchive(fs, ZipArchiveMode.Read) 'エントリを列挙する For Each e As ZipArchiveEntry In a.Entries '展開する時のパスを決定する Dim filePath As String = _ Path.Combine(directoryPath, e.FullName.Replace("/"c, "\"c)) If e.FullName.EndsWith("/") Then 'ディレクトリの時 'ディレクトリを作成する Directory.CreateDirectory(filePath) '更新日時を設定する Directory.SetLastWriteTime(filePath, e.LastWriteTime.DateTime) Else 'ファイルの時 'ディレクトリを作成する Directory.CreateDirectory(Path.GetDirectoryName(filePath)) 'ファイルを開く Using fs2 As FileStream = _ File.Open(filePath, FileMode.CreateNew, FileAccess.Write) 'エントリの内容をファイルにコピーする Using strm As Stream = e.Open() strm.CopyTo(fs2) End Using End Using '更新日時を設定する File.SetLastWriteTime(filePath, e.LastWriteTime.DateTime) End If Next End Using End Using
//using System.IO; //using System.IO.Compression; string zipFilePath = @"C:\test\1.zip"; string directoryPath = @"C:\test\extra\"; //ZIPファイルを開く using (FileStream fs = File.Open(zipFilePath, FileMode.Open, FileAccess.Read)) { //ZipArchiveを作成する using (ZipArchive a = new ZipArchive(fs, ZipArchiveMode.Read)) { //エントリを列挙する foreach (ZipArchiveEntry e in a.Entries) { //展開する時のパスを決定する string filePath = Path.Combine( directoryPath, e.FullName.Replace('/', '\\')); if (e.FullName.EndsWith("/")) { //ディレクトリの時 //ディレクトリを作成する Directory.CreateDirectory(filePath); //更新日時を設定する Directory.SetLastWriteTime(filePath, e.LastWriteTime.DateTime); } else { //ファイルの時 //ディレクトリを作成する Directory.CreateDirectory(Path.GetDirectoryName(filePath)); //ファイルを開く using (FileStream fs2 = File.Open(filePath, FileMode.CreateNew, FileAccess.Write)) { //エントリの内容をファイルにコピーする using (Stream strm = e.Open()) { strm.CopyTo(fs2); } } //更新日時を設定する File.SetLastWriteTime(filePath, e.LastWriteTime.DateTime); } } } }
注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。