トップC# > ZIP圧縮および解凍

ZIP圧縮および解凍

ZIPファイルの解凍

ZIPファイルの解凍は ZipFileクラスを使えば簡単である。

using System;
using System.IO.Compression;

class Download {
    const string DIRZIP = @"d:\src\";
    const string DIREXTRACT = @"d:\dst\";

    static void Main() {
        TimeSpan tSpan = new TimeSpan(1,0,0,0); // 1日
        DateTime dt = new DateTime(2013,10,20);
        for ( ; dt < DateTime.Now; dt += tSpan) {
            string date = dt.ToShortDateString();
            string pathZip = DIRZIP + date.Replace("/","") + ".zip";
            string pathExtract = DIREXTRACT + date.Substring(0,4) + "\\" 
                               + date.Replace("/","") + ".zip";
            try {
                ZipFile.ExtractToDirectory(pathZip, pathExtract);
            } catch (Exception e) {
                Console.WriteLine(pathZip + " " + e.Message);
            }
        }
    }
}

cscでは次のように referenceオプションで dll名を指定する必要がある。

C:\mh\cs>csc /r:System.IO.Compression.FileSystem.dll unzip01.cs

ZIPファイルの作成

ZIPファイルの作成は ZipFileクラスを使えば簡単である。

次のプログラムはディレクトリ C:\gis\tiles\osm\15\28986 下にある全てのファイル(373個)を C:\gis\15_28986.zipに圧縮している。

ただし、この例ではC:\gis\tiles\osm\15\28986 下にあるファイルは全て pngファイルのため、 圧縮効果は期待できないため、ZipFile.CreateFromDirectoryメソッドの第3引数を CompressionLevel.NoCompressionとしている。ZIPファイルに 第4引数はベースディレクトリ名C:\gis\tiles\osm\15\28986を含めないために false としている。 これを含めるときは true とする。

例えば、NTFSフォーマットでは、ディスク上のファイルサイズは 1KB にできるが、 FAT32などでは大容量MicroSDXCでは、4KBまたはそれ以上となる。 従って、サイズの小さいファイルが無数にある場合、そのままでは、効率が悪い。 上の例では、個々のファイルサイズの合計は 2.55MB であるが、ディスクの占有は 3.15MB である。

これに対して15_28986.zipのサイズは 2.59MB となった。 CompressionLevel.Optimalとすると 2.57MB となった。pngファイルの場合、既に圧縮しているから、 更に圧縮しようとしても、僅かな効果しか得られず、圧縮・解凍に時間がかかるため、得策ではない。

// csc /r:System.IO.Compression.FileSystem.dll test\zip01.cs

using System.IO.Compression;

class ZipTest {
    static void Main(string[] args) {
        var srcDir = @"C:\gis\tiles\osm\15\28986";
        var dstPath = @"C:\gis\15_28986.zip";
        ZipFile.CreateFromDirectory(srcDir, dstPath, CompressionLevel.NoCompression, false);
    }
}

ZIPファイルの更新(エントリの追加・削除)

ZIPファイルの更新(エントリの追加・削除)は ZipArchiveクラスおよびZipArchiveEntryクラスを使用する。

プログラム例を下に示す。Zipファイルに関するusing宣言は  using System.IO.Compression; だけでよいが、dll は二つに分かれているため、参照は /r:System.IO.Compression.dll および /r:System.IO.Compression.FileSystem.dll の二つが必要である。

    ZipArchive archive = ZipFile.Open(pathZip, ZipArchiveMode.Update);
    ZipArchiveEntry entry = archive.GetEntry(entryname_del);
    entry.Delete();	// 削除
    archive.CreateEntryFromFile(path_add, entryname_add, CompressionLevel.NoCompression); // 追加
    archive.Dispose();

GetEntry()メソッドで、該当するエントリが存在しなかったときは null が戻される。

ZipArchiveModeは読み書きの時 Update、読み込みは Read、作成時は Create とする。

元々 zipファイルがあるとき、ZipArchiveMode.Createモードでオープンすると、エラーとなる[1]。 同じパス名で全面的に書き換えたい場合には旧ファイルを削除してから、ZipArchiveMode.Createモードでオープンする。

ファイルに展開せず、内容をメモリに読み込む

ファイルに展開せず、ZIPファイルのデータを読み込みたいときには、次のようにする。

//C:\mh\cs>csc /r:System.IO.Compression.FileSystem.dll /r:System.IO.Compression.dll zipfile01.cs
using System;
using System.IO;
using System.IO.Compression;

class LoadZipFile {
    static void Main() {
        using (ZipArchive archive = ZipFile.Open(@"d:\data\20010103.zip", ZipArchiveMode.Read)) {
	    ZipArchiveEntry entry = archive.Entries[0];	// 最初のエントリ
            Console.WriteLine(entry);
            using (StreamReader reader = new StreamReader(entry.Open())) {
          	Console.WriteLine(reader.ReadToEnd());
            }
        } 
    }
}

単純なCSVファイルの場合、行ごとに読み込むには次のように ReadLine()メソッドを使用すればよい。

    using (StreamReader reader = new StreamReader(entry.Open())) {
         while (reader.Peek() >= 0) {
             string[] cols = reader.ReadLine().Split(',');
             for (int n = 0; n < cols.Length; n++)
                 Console.Write(cols[n] + "\t");
             Console.WriteLine("");
         }
    }

特定のエントリをファイルに展開する[2015.3.15]

アーカイブされた全ファイルではなく、指定されたひとつのエントリをファイルに展開するには、 次の例のように、ZipArchiveEntryを求めて、ExtractToFileメソッドで展開する。 この例では、カレントディレクトリ上に展開している。

// csc /r:System.IO.Compression.dll /r:System.IO.Compression.FileSystem.dll src\unzip.cs
using System;
using System.IO;
using System.IO.Compression;

class UnZip {
    static void Main(string[] args) {
        var path = @"C:\gis\tiles_zip\osm\14\14132.zip";
        ZipArchive archive = ZipFile.OpenRead(path);
        ZipArchiveEntry entry = archive.GetEntry(args[0]);
        if (entry == null) {
            Console.WriteLine("not exists!");
            return;
        }
        Console.WriteLine(entry.Length);
        entry.ExtractToFile(entry.FullName, true);      // 同名ファイルがあれば上書き
    }
}

ExtractToFileメソッドの第二引数を false とした場合、同名ファイルが存在したとき、上書きされず、エラーとなる。

エントリの情報を得る

ZipArchiveEntry はそのエントリの書き込み日時、サイズなどの情報を持っている。 自作地図システムでは、最大 1024 タイルのイメージファイルを zip アーカイブとしている。 各タイルの長さ、書き込み日を取り出し、タイル座標をキーとする連想配列に格納するプログラムを下に示す。

class TileInfo {
    public int x, y;
    public int date, length;

    public TileInfo(int x, int y, int date, int length) {
        this.x = x;
        this.y = y;
        this.date = date;
        this.length = length;
    }

    public static Dictionary<long,TileInfo> GetTileInfo(string dirZip) {
        Dictionary<long,TileInfo> dict = new Dictionary<long,TileInfo>();
        foreach (string dirX in Directory.GetDirectories(dirZip)) {
            foreach (string fileY in Directory.GetFiles(dirX)) {
                using (ZipArchive arc = ZipFile.Open(fileY, ZipArchiveMode.Read)) {
                    foreach (ZipArchiveEntry entry in arc.Entries) {
                        string name = entry.Name;   // 7455_3078.png
                        name = name.Substring(0, name.Length-4);
                        string[] items = name.Split('_');
                        int x = int.Parse(items[0]);
                        int y = int.Parse(items[1]);
                        DateTime dt = entry.LastWriteTime.Date;
                        int date = dt.Year * 10000 + dt.Month * 100 + dt.Day;
                        int length = (int)entry.Length;
                        long xy = ((long)x<<32) + y;
                        dict[xy] = new TileInfo(x, y, date, length);
                    }
                }
            }
        }
        return dict;
    }
}

A.リファレンス

[1] ZipFileExtensions.ExtractToFile メソッド (ZipArchiveEntry, String, Boolean)
[2] C#:Zipファイルと画像の読み書き
[3] ZipFile.Open メソッド (String, ZipArchiveMode)
[4] ZipArchiveMode 列挙体