【问题标题】:How to serialize object + compress it and then decompress + deserialize without third-party library?如何在没有第三方库的情况下序列化对象+压缩然后解压缩+反序列化?
【发布时间】:2012-08-18 18:59:45
【问题描述】:

我在内存中有一个大对象,我想将它作为 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%。

我不知道我从以前的版本中得到了什么样的字节:)

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

【问题讨论】:

标签: c# serialization .net-3.5 gzipstream


【解决方案1】:

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

我个人从不认为 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:

关于 .net4.5 的仅供参考:https://stackoverflow.com/a/9808000/717732

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • gzip 和 deflate 中的压缩算法现在普遍不受专利保护。旧的原生 .net 版本不太优化,因为它们的优化较少,而不是因为专利。
    【解决方案3】:

    您的代码中存在错误,并且解释太长,无法发表评论,因此即使它没有回答您的真正问题,我也将其作为答案提供。

    您只需要在关闭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);
    }
    

    【讨论】:

    • 我自己也发现了一个错误。感谢您发布您的答案!刚刚发布编辑:)
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      您可以使用自定义格式化程序

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

      【讨论】:

        猜你喜欢
        • 2017-02-28
        • 2017-04-04
        • 2014-06-07
        • 1970-01-01
        • 1970-01-01
        • 2017-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多