12

データベースにブロブとして保存したい大きなオブジェクトがメモリ内にあります。データベースサーバーは通常ローカルではないため、保存する前に圧縮したいと思います。

これは私が現時点で持っているものです:

using (var memoryStream = new MemoryStream())
{
  using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress))
  {
    BinaryFormatter binaryFormatter = new BinaryFormatter();
    binaryFormatter.Serialize(gZipStream, obj);

    return memoryStream.ToArray();
  }
}

ただし、Total Commander で同じバイトを圧縮すると、常にサイズが少なくとも 50% 削減されます。上記のコードでは、58MB を 48MB に圧縮し、15MB より小さいものはさらに大きくなります。

サードパーティの zip ライブラリを使用する必要がありますか、または .NET 3.5 でこれを行うためのより良い方法はありますか。私の問題に対する他の選択肢はありますか?

編集:

上記のコードにバグが見つかりました。アンジェロ、修正してくれてありがとう。

GZipStream の圧縮はまだ優れていません。TC の 48% 圧縮と比較して、gZipStream による平均圧縮率は 35% です。

以前のバージョンでどのようなバイトを取得していたのかわかりません:)

EDIT2:

圧縮率を 20% から 47% に改善する方法を見つけました。メモリ ストリームを 1 つではなく 2 つ使用する必要がありました。なぜこれが当てはまるのか、誰でも説明できますか?

これは、はるかに優れた圧縮を行う2つのメモリストリームを持つコードです!!!

using (MemoryStream msCompressed = new MemoryStream())
using (GZipStream gZipStream = new GZipStream(msCompressed, CompressionMode.Compress))
using (MemoryStream msDecompressed = new MemoryStream())
{
  new BinaryFormatter().Serialize(msDecompressed, obj);
  byte[] byteArray = msDecompressed.ToArray();

  gZipStream.Write(byteArray, 0, byteArray.Length);
  gZipStream.Close();
  return msCompressed.ToArray();
}
4

5 に答える 5

15

コードにバグがあり、説明がコメントには長すぎるため、実際の質問には答えていませんが、回答として提示します。

閉じた後にmemoryStream.ToArray()のみ呼び出す必要があります。そうしないと、逆シリアル化できない圧縮データが作成されます。GZipStream

修正コードは次のとおりです。

using (var memoryStream = new System.IO.MemoryStream())
{
  using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress))
  {
    BinaryFormatter binaryFormatter = new BinaryFormatter();
    binaryFormatter.Serialize(gZipStream, obj);
  }
  return memoryStream.ToArray();
}

基礎となるバッファーへのGZipStream書き込みはチャンクで行われ、ストリームの最後にフッターも追加されます。これは、ストリームを閉じるときにのみ実行されます。

これは、次のコード サンプルを実行することで簡単に証明できます。

byte[] compressed;
int[] integers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

var mem1 = new MemoryStream();
using (var compressor = new GZipStream(mem1, CompressionMode.Compress))
{
    new BinaryFormatter().Serialize(compressor, integers);
    compressed = mem1.ToArray();
}

var mem2 = new MemoryStream(compressed);
using (var decompressor = new GZipStream(mem2, CompressionMode.Decompress))
{
    // The next line will throw SerializationException
    integers = (int[])new BinaryFormatter().Deserialize(decompressor);
}
于 2012-08-23T10:01:02.343 に答える
2

.NET 3.5 の GZipStream では、圧縮レベルを設定できません。このパラメーターは .NET 4.5 で導入されましたが、それがより良い結果をもたらすかどうか、またはアップグレードが適切かどうかはわかりません。AFAIKの特許により、組み込みアルゴリズムはあまり最適ではありません。したがって、3.5 で圧縮を改善する唯一の方法は、 7zipまたはSharpZipLibによって提供されるSDKなどのサードパーティ ライブラリを使用することです。おそらく、データの圧縮を改善するために、さまざまなライブラリで少し実験する必要があります。

于 2012-08-23T09:55:10.133 に答える
1

使用されるデフォルトの CompressionLevel はOptimal、少なくともhttp://msdn.microsoft.com/en-us/library/as1ff51sによると、GZipStream に「もっと頑張る」ように指示する方法はありません。サードパーティのライブラリの方が良いでしょう。

個人的には、GZipStream が圧縮に関して「良い」とは考えていませんでした。おそらく、メモリ フットプリントを最小化するか、速度を最大化することに力を入れているのでしょう。ただし、WindowsXP/WindowsVista/Windows7 がエクスプローラーで ZIP ファイルをネイティブに処理する方法を見ると、まあ..高速だとも、圧縮率が高いとも言えません..Win7 のエクスプローラーが実際に GZipStream を使用していても驚かないでしょう。 - 全体として、彼らはそれを実装してフレームワークに入れているので、おそらく多くの場所で使用されているため (つまり、HTTP GZIP 処理で使用されているようです)、効率的な処理が必要でした..私の会社は何年も前に .Net が初期の頃に素敵な zip ハンドラーを購入したため、このトピックについて真剣に調査したことはありません。

編集:

その他の参考文献:
http://dotnetzip.codeplex.com/workitem/7159 - ただし、2009 年に「解決済み/解決済み」とマークされています..そのコードで何か興味深いものを見つけることができるでしょうか?

うーん、数分のグーグル検索の後、7Zip はいくつかの C# バインディングを公開しているようです: http://www.splinter.com.au/compressing-using-the-7zip-lzma-algorithm-in/

編集#2:

参考までに.net4.5について: https://stackoverflow.com/a/9808000/717732

于 2012-08-23T09:38:12.410 に答える
1

元の質問は .NET 3.5 に関連していました。3 年後、.NET 4.5 が使用される可能性が高くなりました。私の回答は 4.5 に対してのみ有効です。前述したように、圧縮アルゴリズムは .NET 4.5 で大幅に改善されました。

今日は、データ セットを圧縮してスペースを節約したいと考えました。元の質問と似ていますが、.NET4.5用です。そして、何年も前に double MemoryStream で同じトリックを使用したことを覚えているので、試してみました。私のデータ セットは、多数のハッシュセットと、string/int/DateTime プロパティを持つカスタム オブジェクトのリストを持つコンテナー オブジェクトです。データ セットには約 45,000 個のオブジェクトが含まれており、圧縮せずにシリアル化すると、3500 kB のバイナリ ファイルが作成されます。

現在、GZipStream、質問で説明されているシングルまたはダブルの MemoryStream、または DeflateStream (4.5 で zlib を使用) を使用すると、常に 818 kB のファイルを取得します。したがって、ここで主張したいのは、二重の MemoryStream を使用したトリックが .NET 4.5 で役に立たなくなったことです。

最終的に、私の一般的なコードは次のとおりです。

     public static byte[] SerializeAndCompress<T, TStream>(T objectToWrite, Func<TStream> createStream, Func<TStream, byte[]> returnMethod, Action catchAction)
        where T : class
        where TStream : Stream
     {
        if (objectToWrite == null || createStream == null)
        {
            return null;
        }
        byte[] result = null;
        try
        {
            using (var outputStream = createStream())
            {
                using (var compressionStream = new GZipStream(outputStream, CompressionMode.Compress))
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(compressionStream, objectToWrite);
                }
                if (returnMethod != null)
                    result = returnMethod(outputStream);
            }
        }
        catch (Exception ex)
        {
            Trace.TraceError(Exceptions.ExceptionFormat.Serialize(ex));
            catchAction?.Invoke();
        }
        return result;
    }

たとえば、別のTStreamを使用できるようにします

    public static void SerializeAndCompress<T>(T objectToWrite, string filePath) where T : class
    {
        //var buffer = SerializeAndCompress(collection);
        //File.WriteAllBytes(filePath, buffer);
        SerializeAndCompress(objectToWrite, () => new FileStream(filePath, FileMode.Create), null, () =>
        {
            if (File.Exists(filePath))
                File.Delete(filePath);
        });
    }

    public static byte[] SerializeAndCompress<T>(T collection) where T : class
    {
        return SerializeAndCompress(collection, () => new MemoryStream(), st => st.ToArray(), null);
    }
于 2016-11-08T15:54:40.863 に答える