如何序列化对象+压缩它然后解压缩+反序列化没有第三方库?

时间:2012-08-23 09:27:19

标签: c# serialization .net-3.5 gzipstream

我在内存中有一个大对象,我想将其作为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中有更好的方法。 我问题的其他任何替代方案?

修改

刚刚在上面的代码中发现了一个错误。安吉洛感谢您的解决。

GZipStream压缩仍然不是很好。 与MS 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();
}

5 个答案:

答案 0 :(得分:13)

您的代码中存在错误,并且对于评论的解释时间太长,所以我将其作为答案呈现,即使它没有回答您的真实问题。

您需要在关闭memoryStream.ToArray()后仅调用 {strong>,否则您将创建无法反序列化的压缩数据。

固定代码如下:

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

答案 1 :(得分:2)

.NET 3.5中的GZipStream不允许您设置压缩级别。此参数是在.NET 4.5中引入的,但我不知道它是否会为您提供更好的结果或升级适合您。 由于专利AFAIK,内置算法不是很优化。 因此,在3.5中,获得更好压缩的一种方法是使用第三方库,例如7zipSharpZipLib提供的 SDK 。可能你应该尝试使用不同的lib来更好地压缩你的数据。

答案 2 :(得分:1)

使用的默认CompressionLevel是Optimal,至少根据http://msdn.microsoft.com/en-us/library/as1ff51s,所以没有办法告诉GZipStream“更努力”..对我来说,第三方lib会更好。

我个人从未认为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 abou .net4.5:https://stackoverflow.com/a/9808000/717732

答案 3 :(得分:0)

最初的问题与.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);
    }

答案 4 :(得分:0)

您可以使用自定义格式器

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