我要求每天存档用于构建报告的所有数据。我使用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();
}
答案 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运行,然后运行到最终目标。如果最大的物体不能轻易地序列化到记忆中,我不确定还有什么建议。