[英]How to serialize object + compress it and then decompress + deserialize without third-party library?
我在內存中有一個大對象,我想將其作為 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();
}
}
但是,當我使用 Total Commander 壓縮相同的字節時,它至少總是將大小減少 50%。 使用上面的代碼,它將 58MB 壓縮到 48MB,任何小於 15MB 的東西都會變得更大。
我應該使用第三方 zip 庫還是在 .NET 3.5 中有更好的方法來做到這一點。 我的問題還有其他選擇嗎?
編輯:
剛剛在上面的代碼中發現了一個錯誤。 Angelo 感謝您的修復。
GZipStream 壓縮仍然不是很好。 與 TC 的 48% 壓縮相比,我通過 gZipStream 獲得了平均 35% 的壓縮率。
我不知道我用以前的版本得到了什么樣的字節:)
編輯2:
我已經找到了如何將壓縮率從 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();
}
您的代碼中有一個錯誤,而且解釋對於評論來說太長了,所以即使它沒有回答您的真正問題,我也將其作為答案呈現。
只有在關閉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
以塊的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);
}
.NET 3.5 中的 GZipStream 不允許您設置壓縮級別。 這個參數是在.NET 4.5 中引入的,但不知道它是否會給您帶來更好的結果或升級是否適合您。 由於專利 AFAIK,內置算法不是很優化。 因此,在 3.5 中獲得更好壓縮的唯一方法是使用第三方庫,例如7zip或SharpZipLib提供的SDK 。 也許你應該嘗試不同的庫一點點地得到更好的數據壓縮。
使用的默認 CompressionLevel 是Optimal
,至少根據http://msdn.microsoft.com/en-us/library/as1ff51s ,所以沒有辦法告訴 GZipStream “更加努力”.. 對我來說似乎第 3 方庫會更好。
我個人從不認為 GZipStream 在壓縮方面是“好”的——可能他們努力最小化內存占用或最大化速度。 然而,看到 WindowsXP/WindowsVista/Windows7 如何在資源管理器中本地處理 ZIP 文件 - 好吧..我不能說它既不快,也沒有很好的壓縮......如果 Win7 中的資源管理器實際上使用 GZipStream,我不會感到驚訝- 總而言之,他們已經實現並放入框架中,所以可能他們在很多地方使用它(即,似乎用於HTTP GZIP 處理),所以我會遠離它,我需要一個有效的處理..我從未對這個主題進行過任何認真的研究,因為我的公司多年前在 .Net 還處於早期階段時購買了一個不錯的 zip-handler。
編輯:
更多參考:
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
最初的問題與 .NET 3.5 有關。 三年后,.NET 4.5 更有可能被使用,我的回答只對 4.5 有效。 正如前面提到的,壓縮算法在 .NET 4.5 中得到了很好的改進
今天,我想壓縮我的數據集以節省一些空間。 與原始問題非常相似,但適用於 .NET4.5。 因為我記得多年前對雙 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);
}
您可以使用自定義格式化程序
public class GZipFormatter : IFormatter
{
IFormatter formatter;
public GZipFormatter()
{
this.formatter = new BinaryFormatter();
}
public GZipFormatter(IFormatter formatter)
{
this.formatter = formatter;
}
ISurrogateSelector IFormatter.SurrogateSelector { get => formatter.SurrogateSelector; set => formatter.SurrogateSelector = value; }
SerializationBinder IFormatter.Binder { get => formatter.Binder; set => formatter.Binder = value; }
StreamingContext IFormatter.Context { get => formatter.Context; set => formatter.Context = value; }
object IFormatter.Deserialize(Stream serializationStream)
{
using (GZipStream gZipStream = new GZipStream(serializationStream, CompressionMode.Decompress))
{
return formatter.Deserialize(gZipStream);
}
}
void IFormatter.Serialize(Stream serializationStream, object graph)
{
using (GZipStream gZipStream = new GZipStream(serializationStream, CompressionMode.Compress))
using (MemoryStream msDecompressed = new MemoryStream())
{
formatter.Serialize(msDecompressed, graph);
byte[] byteArray = msDecompressed.ToArray();
gZipStream.Write(byteArray, 0, byteArray.Length);
gZipStream.Close();
}
}
那么你可以這樣使用:
IFormatter formatter = new GZipFormatter();
using (Stream stream = new FileStream(path...)){
formatter.Serialize(stream, obj);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.