仅在达到阈值后进行Gzip?

时间:2015-10-26 15:01:36

标签: c# protobuf-net gzipstream

我要求每天存档用于构建报告的所有数据。我使用gzip压缩大部分数据,因为一些数据集可能非常大(10mb +)。我将每个protobuf图写入文件。我还将一组固定的已知小对象类型列入白名单,并在我阅读时添加了一些代码来检测文件是否被gzip压缩。这是因为压缩后的小文件实际上可以大于未压缩文件。

不幸的是,由于数据的性质,我可能只有一些较大对象类型的元素,而白名单方法可能会有问题。

是否有将对象写入流,并且只有达到阈值(如8kb),然后压缩它?我事先并不知道对象的大小,有时我会有一个IEnumerable<T>的对象图,其大小可能很大。

编辑: 代码很基本。我确实略过了将其存储在filestream db表中的事实。这对于实现目的来说并不重要。我删除了一些无关的代码。

public Task SerializeModel<T>(TransactionalDbContext dbConn, T Item, DateTime archiveDate, string name)
{
    var continuation = (await dbConn
        .QueryAsync<PathAndContext>(_getPathAndContext, new {archiveDate, model=name})
        .ConfigureAwait(false))
        .First();

    var useGzip = !_whitelist.Contains(typeof(T));

    using (var fs = new SqlFileStream(continuation.Path, continuation.Context, FileAccess.Write,
        FileOptions.SequentialScan | FileOptions.Asynchronous, 64*1024))
    using (var buffer = useGzip ? new GZipStream(fs, CompressionLevel.Optimal) : default(Stream))
    {
        _serializerModel.Serialize(stream ?? fs, item);
    }

    dbConn.Commit();
}

2 个答案:

答案 0 :(得分:1)

在序列化过程中,您可以使用中间流来完成您的要求。这样的事情可以完成这项工作

class SerializationOutputStream : Stream
{
    Stream outputStream, writeStream;
    byte[] buffer;
    int bufferedCount;
    long position;
    public SerializationOutputStream(Stream outputStream, int compressTreshold = 8 * 1024)
    {
        writeStream = this.outputStream = outputStream;
        buffer = new byte[compressTreshold];
    }
    public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
    public override void SetLength(long value) { throw new NotSupportedException(); }
    public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
    public override bool CanRead { get { return false; } }
    public override bool CanSeek { get { return false; } }
    public override bool CanWrite { get { return writeStream != null &&  writeStream.CanWrite; } }
    public override long Length { get { throw new NotSupportedException(); } }
    public override long Position { get { return position; } set { throw new NotSupportedException(); } }
    public override void Write(byte[] buffer, int offset, int count)
    {
        if (count <= 0) return;
        var newPosition = position + count;
        if (this.buffer == null)
            writeStream.Write(buffer, offset, count);
        else
        {
            int bufferCount = Math.Min(count, this.buffer.Length - bufferedCount);
            if (bufferCount > 0)
            {
                Array.Copy(buffer, offset, this.buffer, bufferedCount, bufferCount);
                bufferedCount += bufferCount;
            }
            int remainingCount = count - bufferCount;
            if (remainingCount > 0)
            {
                writeStream = new GZipStream(outputStream, CompressionLevel.Optimal);
                try
                {
                    writeStream.Write(this.buffer, 0, this.buffer.Length);                            
                    writeStream.Write(buffer, offset + bufferCount, remainingCount);
                }
                finally { this.buffer = null; }
            }
        }
        position = newPosition;
    }
    public override void Flush()
    {
        if (buffer == null)
            writeStream.Flush();
        else if (bufferedCount > 0)
        {
            try { outputStream.Write(buffer, 0, bufferedCount); }
            finally { buffer = null; }
        }
    }
    protected override void Dispose(bool disposing)
    {
        try
        {
            if (!disposing || writeStream == null) return;
            try { Flush(); }
            finally { writeStream.Close(); }
        }
        finally
        {
            writeStream = outputStream = null;
            buffer = null;
            base.Dispose(disposing);
        }
    }
}

并像这样使用

using (var stream = new SerializationOutputStream(new SqlFileStream(continuation.Path, continuation.Context, FileAccess.Write,
        FileOptions.SequentialScan | FileOptions.Asynchronous, 64*1024)))
    _serializerModel.Serialize(stream, item);

答案 1 :(得分:0)

  

数据集可能非常大(10mb +)

在大多数设备上,这不是很大。在决定是否压缩之前,是否有理由无法读取整个对象?另请注意@Niklas建议在决定是否压缩之前读入一个缓冲区的数据(例如8K)。

  

这是因为压缩后的小文件实际上可以大于未压缩文件。

使小文件可能更大的东西是ZIP标题,特别是字典。某些ZIP库允许您在压缩和解压缩时使用已知的自定义词典。多年前我使用了SharpZipLib

在编码和测试方面,使用这种方法需要付出更多努力。如果你认为这种好处是值得的,它可能会提供最好的方法。

注意无论您采用何种路径,您都将使用存储设备的块大小的倍数来物理存储数据。

  

如果对象是1字节或100mb我不知道

请注意,协议缓冲区为not really designed for large data sets

  

协议缓冲区不是为处理大型邮件而设计的。作为一般经验法则,如果您处理的是每个大于兆字节的邮件,则可能需要考虑其他策略。

     

也就是说,协议缓冲区非常适合处理大型数据集中的单个消息。通常,大型数据集实际上只是一小部分的集合,其中每个小块可能是一个结构化的数据。

如果您的最大对象可以轻松地序列化到内存中,首先将其序列化为MemoryStream,然后将该MemoryStream写入最终目标,或者通过GZipStream运行,然后运行到最终目标。如果最大的物体不能轻易地序列化到记忆中,我不确定还有什么建议。