C#の標準機能でデータを圧縮・展開する

C#には結構昔から標準機能として「DeflateStream」と「GZipStream」というデータを圧縮する2種類のライブラリが実装されています。

MSDNに説明が書いてあるのですが読んでもよくわからないと思うので簡単にまとめてみました。

名前 説明
Deflate 可逆圧縮アルゴリズムの名前
GZipStream Defrate圧縮 + ヘッダーとフッターを付与したもの

両方とも中身の実装は「zlib ライブラリ」を使用しているみたいです。 「.NET Framework 4.5以降は」と書いてありますがまぁ大抵の場合、標準的な実装が利用できそうです。

なので、Deflateで圧縮すると純粋なデータ圧縮、GZipStreamで圧縮するとDelfateの圧縮データ + ヘッダー(+フッダー)になります。

使い分けの方針ですが、Deflateだとヘッダーなどがない只のバイナリデータなので元が何のデータが分からなくなるので身元が明らかなローカルシステム内のストレージや、エンドポイント間のデータ転送などで限定的に使用します。GZipはファイルに保存したりするときに.zipや.gzipなど拡張子を付けて保存したり間をあけて生成者と利用者が異なるケースで使用することが多いと思います。

当方説明以外でも説明しているページがあったのですが掲載コードがやや微妙だったので余計なことをせず簡潔なライブラリをC#で実装してみました。

確認環境

確認環境は以下の通り

  • .NET Core 3.1
  • Unity 2019.2.17f1
  • Visual Studio2019
  • Windows 10

(UnityはEditorとWindowsで動作確認済み。Android/iOSの実機動作は未確認、たぶん動くの精神)

DataOperationクラス:圧縮・解凍ライブラリ

文字列を直接圧縮・解凍する方法と、バイナリと圧縮・解凍する処理を提供するクラスを"DataOperation"クラスにまとめました。

// DataOperation.cs

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

/// <summary>
/// デフレートアルゴリズムを使用したデータ圧縮・解凍昨日を定義します。
/// </summary>
public static class DataOperation
{
    /// <summary>
    /// 文字列を圧縮しバイナリ列として返します。
    /// </summary>
    public static byte[] CompressFromStr(string message) => Compress(Encoding.UTF8.GetBytes(message));

    /// <summary>
    /// バイナリを圧縮します。
    /// </summary>
    public static byte[] Compress(byte[] src)
    {
        using (var ms = new MemoryStream())
        {
            using (var ds = new DeflateStream(ms, CompressionMode.Compress, true/*msは*/))
            {
                ds.Write(src, 0, src.Length);
            }

            // 圧縮した内容をbyte配列にして取り出す
            ms.Position = 0;
            byte[] comp = new byte[ms.Length];
            ms.Read(comp, 0, comp.Length);
            return comp;
        }
    }

    /// <summary>
    /// 圧縮データを文字列として復元します。
    /// </summary>
    public static string DecompressToStr(byte[] src) => Encoding.UTF8.GetString(Decompress(src));

    /// <summary>
    /// 圧縮済みのバイト列を解凍します。
    /// </summary>
    public static byte[] Decompress(byte[] src)
    {
        using (var ms = new MemoryStream(src))
        using (var ds = new DeflateStream(ms, CompressionMode.Decompress))
        {
            using (var dest = new MemoryStream())
            {
                ds.CopyTo(dest);

                dest.Position = 0;
                byte[] decomp = new byte[dest.Length];
                dest.Read(decomp, 0, decomp.Length);
                return decomp;
            }
        }
    }
}

"DeflateStream" の個所を "GZipStream" に変更すればGZipStreamが同じように使用できます。

用途によると思うので選べるように汎用性を作りこんでもいいのかもしれません。

使い方

上記のクラスの使い方は以下のとおりです。プレーンテキストなら大抵半分よりは小さくなる場合が多いです(データが10バイトのように短すぎると逆にサイズが大きくなります)

// AppMain.cs

using System;
using System.Text;

internal static void Main(string[] args)
{
    // 圧縮対象の文字列
    string message = "ああいいううええおおかかききくくけけここ";

    // 圧縮しない場合の文字のバイト配列
    byte[] planeBytes = Encoding.UTF8.GetBytes(message);
    ShowBytes(planeBytes);
    // > 0xE3 0x81 0x82 0xE3 0x81... 60byte

    // --- ★圧縮する ---

    // 文字列を圧縮する
    byte[] compressed = DataOperation.CompressFromStr(message);
    ShowBytes(compressed);
    // > 0x7B 0xDC 0xD8 0xF4 0x18... 30byte(半分に減ってる

    // バイト配列を圧縮
    byte[] compressed2 = DataOperation.Compress(planeBytes);
    ShowBytes(compressed2);
    // > 0x7B 0xDC 0xD8 0xF4 0x18... 30byte(半分に減ってる

    // --- ★元に戻す ---

    // 圧縮した文字列を元に戻す
    string restore = DataOperation.DecompressToStr(compressed);
    Console.WriteLine(restore);
    // > ああいいううええおおかかききくくけけここ

    // バイト配列を元に戻す
    string restore2 = Encoding.UTF8.GetString(DataOperation.Decompress(compressed2));
    Console.WriteLine(restore2);
    // > ああいいううええおおかかききくくけけここ
}

// バイト配列の内容をコンソールに表示する
public static void ShowBytes(byte[] bs)
{
    Array.ForEach(bs, b => Console.Write($"0x{b:X2} "));
    Console.WriteLine("");
}

それぞれ変換 → 元に戻す操作ができていると思います。

大した事ないので短めですが以上です。

参考資料

dobon.net: GZIPやデフレートでファイルを圧縮する

Gushwell's Dev Notes: データの圧縮と展開

MSDN: DeflateStream クラス