如何在没有第三方库的情况下序列化对象并进行压缩,然后解压缩和反序列化?

13

我在内存中有一个很大的对象,希望将其保存为数据库中的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%。

我不知道以前版本输出了哪种类型的字节 :)

第二次编辑:

我已经找到了如何将压缩从20%提高到47%。 我必须使用两个Memory Stream而不是一个!有人能解释为什么吗?

此处是使用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
5个回答

18

你的代码存在一个错误,而且解释太长无法在评论中呈现,因此我将其作为答案呈现,尽管它并没有回答你的实际问题。

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

我自己发现了一个错误。谢谢你发布你的答案!我只是在发布编辑 :) - Marek

2

从.NET 3.5开始,GZipStream不允许您设置压缩级别。这个参数是在.NET 4.5中引入的,但我不知道它是否会给您更好的结果或升级是否适合您。

由于专利的原因,内置算法并不是非常优化。

因此,在3.5中获得更好的压缩的唯一方法是使用第三方库,如7zip提供的SDKSharpZipLib。可能需要尝试使用不同的库来获得更好的数据压缩效果。


1
现在gzip和deflate中的压缩算法通常不受专利限制。旧的本地.NET版本不太优化,这不是由于专利原因。 - Bryan Legend

1

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

个人认为GZipStream在压缩方面并不“好”-可能他们花了很多功夫来最小化内存占用或者最大化速度。然而,看到WindowsXP/WindowsVista/Windows7如何本地处理ZIP文件 - 嗯..我不能说它既快又有好的压缩效果..如果Win7中的资源管理器实际上使用GZipStream,我也不会感到惊讶 - 毕竟他们已经将其实现并放入框架中,所以可能在许多地方使用它(例如,在HTTP GZIP handling中似乎被使用),因此如果需要高效处理,我会远离它。我从未在这个主题上进行过任何深入研究,因为在.Net早期时代,我的公司购买了一个不错的zip处理程序。

编辑:

更多参考:
http://dotnetzip.codeplex.com/workitem/7159 - 但在2009年被标记为“关闭/已解决”..也许你会在那段代码中找到一些有趣的东西?
嘿,在谷歌上搜索了几分钟后,似乎7Zip公开了一些C#绑定:http://www.splinter.com.au/compressing-using-the-7zip-lzma-algorithm-in/ 编辑#2:
只是关于.NET4.5的FYI:https://dev59.com/PGkw5IYBdhLWcg3wkLX2#9808000

1
原始问题与.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的文件。因此,我只想在这里强调,双重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);
    }

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接