2012-08-23 73 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中做到這一點。 我的問題的任何其他替代方案?

編輯:

剛剛發現上面代碼中的錯誤。安傑洛謝謝你的解決。

GZipStream壓縮仍然不是很好。 與TC 48%壓縮相比,我獲得了gZipStream的平均35%壓縮。

我不知道什麼樣的字節我得到了與以前的版本:)

EDIT2:

我已經找到了如何從20%提高壓縮47%。 我不得不使用兩個內存流而不是一個!任何人都可以解釋這是爲什麼嗎?

這裏是一個代碼與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中只有一種獲得更好壓縮的方法是使用第三方庫如SDK7zipSharpZipLib提供。也許你應該嘗試一下不同的庫,以獲得更好的你的數據的壓縮。

+1

gzip和deflate中的壓縮算法現在通常不受專利權限制。原生的.net版本不太理想,因爲它們的優化程度較低,不是由於專利。 –

1

使用的默認CompressionLevel是Optimal,至少根據http://msdn.microsoft.com/en-us/library/as1ff51s,所以沒有辦法告訴GZipStream爲「更加努力」。它似乎對我來說是一個第三方的lib會更好。

我個人從未認爲GZipStream在壓縮方面「很好」 - 可能他們會盡量減少內存佔用或最大化速度。然而,看到WindowsXP/WindowsVista/Windows7如何在資源管理器中本地處理ZIP文件 - 呃..我不能說它速度很快,也沒有良好的壓縮。如果Win7中的資源管理器實際上使用GZipStream,我不會感到驚訝 - 總而言之,他們已經實現了它並將其放入框架,所以他們可能在很多地方使用它(例如,似乎用於HTTP GZIP handling),所以我會遠離它,我需要一個有效的處理。我從來沒有做過關於這個話題的任何認真的研究,因爲我的公司在很多年前購買了一個漂亮的zip處理程序,當時.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

在您的代碼有一個錯誤,並且解釋對於評論來說太長,所以我將它作爲答案呈現,即使它沒有回答您真正的問題。

你需要調用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); 
} 
+0

發現了一個我自己的錯誤。感謝您發佈您的答案!只是發佈編輯:) – Marek

0

原來的問題涉及到.NET 3.5。 三年後,.NET 4.5更有可能被使用,我的答案僅適用於4.5。正如前面提到的那樣,壓縮算法在.NET 4.5中得到了很好的改進。如今,我想壓縮我的數據集以節省一些空間。與原始問題類似,但是對於.NET4.5。 因爲我記得很多年前用雙MemoryStream使用相同的技巧,我只是試了一下。 我的數據集是一個容器對象,其中包含許多哈希集以及帶有字符串/整數/日期時間屬性的自定義對象列表。該數據集包含大約45 000個對象,並且在沒有壓縮的情況下序列化時,會創建一個3500 kB的二進制文件。

現在,使用GZipStream,單個或兩個MemoryStream的問題描述,或與DeflateStream(它使用4.5中的zlib),我總是得到一個818 kB的文件。 所以我只想堅持這裏,而不是使用.NET 4.5的雙重MemoryStream技巧。

最終,我的代碼一般是如下:

 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; 
    } 

,這樣我可以使用不同的T流,例如

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