Избегайте копирования сжатых данных при использовании DeflateStream
Предположим, мы дали API-функцию f(Stream s) для помещения двоичных данных, содержащихся в потоке, в базу данных. Я хочу поместить файл в базу данных, используя f, но я хочу сжать данные заранее. Поэтому я подумал, что могу сделать следующее:
var fileStream= File.OpenRead(path);
using(var dstream = new DeflateStream(fileStream, CompressionLevel.Optimal))
f(dstream);
Но похоже DeflateStream
только пишет в поток fileStream
но не читает из него при сжатии. Во всех найденных примерах CopyTo
Метод потока был использован для сжатия или распаковки. Но это будет означать, что я должен сохранить копию сжатых данных в памяти, прежде чем передать ее f
например, вот так:
var memoryStream = new MemoryStream();
using(var fileStream= File.OpenRead(path))
using(var dstream = new DeflateStream(memoryStream, CompressionLevel.Optimal)) {
fileStream.CopyTo(dstream);
memoryStream.Seek(0, SeekOrigin.Begin);
f(memoryStream);
}
Есть ли способ избежать использования MemoryStream?
Обновление Ради настойчивости некоторых комментаторов я добавлю полный пример:
using System;
using System.IO;
using System.IO.Compression;
public class ThisWouldBeTheDatabaseClient {
public void f(Stream s) {
// some implementation I don't have access to
// The only thing I know is that it reads data from the stream in some way.
var buffer = new byte[10];
s.Read(buffer,0,10);
}
}
public class Program {
public static void Main() {
var dummyDatabaseClient = new ThisWouldBeTheDatabaseClient();
var dataBuffer = new byte[1000];
var fileStream= new MemoryStream( dataBuffer ); // would be "File.OpenRead(path)" in real case
using(var dstream = new DeflateStream(fileStream, CompressionLevel.Optimal))
dummyDatabaseClient.f(dstream);
}
}
Операция чтения в фиктивной реализации f
выдает исключение: InvalidOperationException: чтение из потока сжатия не поддерживается. Завершая обсуждение в комментариях, я предполагаю, что желаемое поведение невозможно с DeflateStream
но есть альтернативы в сторонних библиотеках.
2 ответа
Ты можешь использовать SharpCompress
за это. это DeflateStream
позволяет читать сжатые данные на лету, что именно то, что вы хотите.
Вот полный пример, основанный на сэре Руфо:
using System;
using System.IO;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
using System.Linq;
public class Program
{
public static void Main()
{
var dataBuffer = Enumerable.Range(1, 50000).Select(e => (byte)(e % 256)).ToArray();
using (var dataStream = new MemoryStream(dataBuffer))
{
// Note: this refers to SharpCompress.Compressors.Deflate.DeflateStream
using (var deflateStream = new DeflateStream(dataStream, CompressionMode.Compress))
{
ConsumeStream(deflateStream);
}
}
}
public static void ConsumeStream(Stream stream)
{
// Let's just prove we can reinflate to the original data...
byte[] data;
using (var decompressed = new MemoryStream())
{
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
decompressor.CopyTo(decompressed);
}
data = decompressed.ToArray();
}
Console.WriteLine("Reinflated size: " + data.Length);
int errors = 0;
for (int i = 0; i < data.Length; i++)
{
if (data[i] != (i + 1) % 256)
{
errors++;
}
}
Console.WriteLine("Total errors: " + errors);
}
}
Или используя ваш пример кода:
using System;
using System.IO;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
public class ThisWouldBeTheDatabaseClient {
public void f(Stream s) {
// some implementation I don't have access to
// The only thing I know is that it reads data from the stream in some way.
var buffer = new byte[10];
s.Read(buffer,0,10);
}
}
public class Program {
public static void Main() {
var dummyDatabaseClient = new ThisWouldBeTheDatabaseClient();
var dataBuffer = new byte[1000];
var fileStream= new MemoryStream( dataBuffer ); // would be "File.OpenRead(path)" in real case
using(var dstream = new DeflateStream(
fileStream, CompressionMode.Compress, CompressionLevel.BestCompression))
dummyDatabaseClient.f(dstream);
}
}
Это теперь не вызывает исключения и будет обслуживать сжатые данные.
DeflateStream
это просто оболочка и нуждается в потоке для сжатых данных. Таким образом, вы должны использовать два потока.
Есть ли способ избежать использования MemoryStream?
Да.
Вам нужен поток для хранения временных данных без использования (слишком большого количества) памяти. Вместо того, чтобы использовать MemoryStream
Вы можете использовать временный файл для этого.
Для ленивых людей (таких как я в первую очередь) давайте создадим класс, который будет вести себя в основном как MemoryStream
public class TempFileStream : FileStream
{
public TempFileStream() : base(
path: Path.GetTempFileName(),
mode: FileMode.Open,
access: FileAccess.ReadWrite,
share: FileShare.None,
bufferSize: 4096,
options: FileOptions.DeleteOnClose | FileOptions.Asynchronous | FileOptions.Encrypted | FileOptions.RandomAccess)
{
}
}
Важная часть здесь FileOptions.DeleteOnClose
который удалит временный файл при удалении потока.
А потом используй
using (var compressedStream = new TempFileStream())
{
using (var deflateStream = new DeflateStream(
stream: compressedStream,
compressionLevel: CompressionLevel.Optimal,
leaveOpen: true))
using (var fileStream = File.OpenRead(path))
{
fileStream.CopyTo(deflateStream);
}
f(compressedStream);
}