2012-08-23 7 views
9

私はデータベースにBLOBとして保存したい大きなオブジェクトをメモリに持っています。 データベースサーバは通常ローカルではないので、保存する前に圧縮したいです。オブジェクトを直列化して圧縮し、サードパーティのライブラリを使用せずに解凍+逆シリアル化する方法はありますか?

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

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

    return memoryStream.ToArray(); 
    } 
} 

私は総司令官と同じバイトを圧縮するときしかし、それは、少なくとも50%で、常にサイズをダウンカットします。上記のコードでは58MBから48MBに圧縮され、15MBより小さいものはさらに大きくなります。

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

EDIT:

ただ、上記のコードにバグを発見しました。あなたの修正のためにアンジェロに感謝します。

GZipStreamの圧縮率はそれほど高くありません。 私は、TC 48%圧縮と比較して、gZipStreamで平均35%圧縮を取得します。

私は、以前のバージョンで出てきたバイトの種類見当がつかない:)

EDIT2:

47%に20%から圧縮を向上させるために、どのように私を発見しました。 私は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(); 
} 
+1

私はhttp://www.icsharpcodeを使用します。net/opensource/sharpziplib/Download.aspxを成功させました。 – Asken

答えて

2

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

+1

gzipとdeflateの圧縮アルゴリズムは、現在、特許によって妨げられていません。古いネイティブの.netバージョンは、特許のためにではなく、最適化されているため、あまり最適ではありません。 –

1

デフォルトがCompressionLevelは、少なくともhttp://msdn.microsoft.com/en-us/library/as1ff51sによると、Optimalなので、サードパーティ製のlibには良いだろうと私のためのようだ..「頑張り」するGZipStreamを伝える方法がありません。

私は個人的にGZipStreamを圧縮の点で「良い」とは考えていませんでした。おそらくメモリのフットプリントを最小限に抑えたり、スピードを最大限に高めることに努力しました。しかし、エクスプローラでWindowsXP/WindowsVista/Windows7がネイティブにZIPファイルをどのように処理するのかを見ると、高速でも良い圧縮もないとは言えません。Win7のエクスプローラが実際にGZipStreamを使用していても驚かないでしょう彼らはそれを実装してフレームワークに入れました。おそらく彼らは多くの場所でそれを使用しています(つまり、HTTP GZIP handlingで使用されているようです)ので、私は効率的な処理が必要です。何年も前に.Netが初期の時に私の会社が素晴らしいジップハンドラーを買ったので、このトピックで深刻な研究をしたことはありませんでした。

編集:

より引用文献:
http://dotnetzip.codeplex.com/workitem/7159 - しかし、2009年に「クローズ/解決」としてマークされた..多分あなたはそのコードで何か面白いものを見つけるのだろうか?

あわや、グーグルの数分後に、7zipを、いくつかのC#バインディングを公開するようだ:http://www.splinter.com.au/compressing-using-the-7zip-lzma-algorithm-in/

編集#2:

ちょうどFYI .net4.5アボ:https://stackoverflow.com/a/9808000/717732

11

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

あなたはがそうでなければ、あなたがデシリアライズすることはできません圧縮されたデータを作成しているGZipStreamを閉じた後memoryStream.ToArray()のみを呼び出す必要があります。

固定コードは次のとおりです。

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); 
} 
+0

自分自身もバグを見つけました。あなたの答えを投稿してくれてありがとう!ちょうど投稿していた編集:) – Marek

0

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

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

質問に記載されているように1つまたは2つのMemoryStreamを使用するGZipStream、または4.5でzlibを使用するDeflateStreamを使用すると、私は常に818 KBのファイルを取得します。 だから、ここでは、ダブルメモリでのトリックではなく、.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); 
    } 
関連する問題