C#でZipファイルを解凍・圧縮する

C#でZipファイルを扱う方法を紹介したいと思います。この機能は、結構最近追加されたため.NET4.5以上で利用可能です。

簡単なファイル解凍・圧縮

まずプロジェクトの参照の追加より、以下のアセンブリを参照に追加します。

System.IO.Compression.FileSystem.dll

圧縮・解凍を単純にするだけであれば、System.IO.Compression の参照は必要ありません。

f:id:Takachan:20171217101300p:plain

ファイルの解凍

以下の構成のzipファイルを、E:\Test.zipに配置します。

Test.zip/
  /Test_001.txt
  /Test_002.txt
  /Text_003.txt
  /Dir_001
    /Sub_Text_001.txt
    /Sut_Test_002.txt
    /Sut_Text_003.txt

上記zipファイルをE:\Testフォルダに解凍するコードは以下の通りです。1行で記述することができます。第3引数にエンコードを指定することもできます。解凍時にマルチバイト文字が含まれていて結果が文字化けする場合、正しく指定する必要があります。

using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            ZipFile.ExtractToDirectory(@"E:\Test.zip", @"E:\Test";
            //ZipFile.ExtractToDirectory(@"E:\Test.zip", @"E:\Test", Encoding.UTF8);
        }
    }
}

実行後のE:\Testの内容はzipの内容がそのまま展開されます。

E:\TEST/
  /Test_001.txt
  /Test_002.txt
  /Text_003.txt
  /Dir_001
    /Sub_Text_001.txt
    /Sut_Test_002.txt
    /Sut_Text_003.txt

ファイルの圧縮

前述のE:\TextをE:\Test.zipへ圧縮します。ZipFile.CreateFromDirectoryを使って1行で記述できます。

using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            ZipFile.CreateFromDirectory(@"E:\Test", @"E:\Test.zip");

            // (2) ZipFile.CreateFromDirectory(@"E:\Test", @"E:\Test.zip",
            //           CompressionLevel.Fastest, true);

            // (3) ZipFile.CreateFromDirectory(@"E:\Test", @"E:\Test.zip",
            //           CompressionLevel.Fastest, true, Encoding.UTF8);
        }
    }
}

実行後は以下構造のZipファイルが作成されます。

Test.zip/
  /Test_001.txt
  /Test_002.txt
  /Text_003.txt
  /Dir_001
    /Sub_Text_001.txt
    /Sut_Test_002.txt
    /Sut_Text_003.txt

既に圧縮先にファイルがあった場合、以下例外が発生します。

System.IO.IOException: 'ファイル 'E:\Test.zip' は既に存在します。'

また、コード中の(2)のオーバーロードにて、第3引数にて圧縮レベルを以下の3種類から選べます。

識別子 意味
Optimal 高い圧縮率
Fastest 低い圧縮率
NoCompression 無圧縮

また、第4引数で、ルートディレクトリをアーカイブに含めるかどうかを指定できます。指定しない場合はfalseとなっていて、trueを指定すると以下のようにzipにルートフォルダが作成されます。

Test.zip/
  /Test // ★この階層を含むzipになる
    /Test_001.txt
    /Test_002.txt
    /Text_003.txt
    /Dir_001
      /Sub_Text_001.txt
      /Sut_Test_002.txt
      /Sut_Text_003.txt

複雑なアーカイブ操作

こちらでは、zipファイルの内容を1つ1つ表示したり、ファイルを別々の場所から集めてzipにしたりなど、アーカイブに対する自由な操作を行います。

今回は、System.IO.Compressionを参照に追加します。

f:id:Takachan:20171217101218p:plain

アーカイブ内のファイルを列挙する

ZipFile.Openを呼び出すとZipArchiveクラスを得られそこからZipFileに対する細かい操作を行えるようになります。

以下例では、ZipArchive.Entriesより得られるZipArchiveEntry.FullNameによって圧縮ファイル内のファイルをすべて列挙します。

using System;
using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            //ZipArchive archive = ZipFile.Open(@"E:\Test.zip", ZipArchiveMode.Read);
            using (ZipArchive archive = ZipFile.OpenRead(@"E:\Test.zip"))
            {
                foreach(ZipArchiveEntry entry in archive.Entries)
                {
                    Console.WriteLine(entry.FullName);
                }
            }
        }
    }
}

上記コードを実行すると以下出力になります。

> Dir_001/
> Dir_001/Sub_Text_001.txt
> Dir_001/Sut_Test_002.txt
> Dir_001/Sut_Text_003.txt
> Test_001.txt
> Test_002.txt
> Text_003.txt

アーカイブから1つだけファイルを取り出す

これまで取り扱っていたアーカイブ内から"Dir_001/Sut_Text_003.txt"を取り出してHDDに保存します。

先ほどとコードはほとんど同じで、ZipArchiveEntryのExtractToFileをファイルパス付で呼び出します。

using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            using (ZipArchive archive = ZipFile.OpenRead(@"E:\Test.zip"))
            {
                foreach(ZipArchiveEntry entry in archive.Entries)
                {
                    if(entry.FullName == "Dir_001/Sut_Text_003.txt")
                    {
                        entry.ExtractToFile(@"E:\" + entry.Name); // ここでこのファイルの保存先を指定する。
                    }
                }
            }
        }
    }
}

アーカイブにファイルを追加する

アーカイブ内に1つファイルを追加するには、ZipFile.Open時に書き込みモード = ZipArchiveMode.Update を指定してアーカイブを開きます。そのあとに、ZipArchive.CreateEntryを用いてStreamを取得しそこへ書き込みたいテキストを書き込んでいきます。

using System.IO;
using System.IO.Compression;

namespace ZipTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            using (ZipArchive archive = ZipFile.Open(@"E:\Test.zip", ZipArchiveMode.Update))
            //using (ZipArchive archive = ZipFile.OpenRead(@"E:\Test.zip"))
            {
                ZipArchiveEntry item = 
                    archive.CreateEntry("Dir_001/Sub_Text_004.txt",
                        CompressionLevel.NoCompression);

                // (1) 自分でStreamに書き込む
                using (Stream stream = item.Open())
                using (var sw = new StreamWriter(stream))
                {
                    sw.WriteLine("New Text");
                }

                // (2) ファイルを直接指定してファイルを追加
                archive.CreateEntryFromFile(@"E:\Sub_Text_005.txt",
                    "Dir_002/Sub_Text_005.txt", CompressionLevel.NoCompression);

                archive.CreateEntryFromFile(@"E:\Sub_Text_006.txt",
                    "Dir_002/Sub_Text_006.txt", CompressionLevel.NoCompression);
            }
        }
    }
}

(1)では自分でStreamを操作してふぃあるを追加しています。(2)ではディスク上の既存のファイルを指定してアーカイブに追加しています。(1)のほうが自由度が高いですが、(2)のほうが操作が簡単です。

C#でZipファイルを操作する方法は以上です。