[英]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.