先决条件
我有一个流rawStream
和一个获取流并将其读到最后的方法,让我们这样说:
Task UploadFile(Stream stream) { ... }
目前,此方法已成功使用,如下所示:
await UploadFile(rawStream);
我想做什么
现在我需要将GZip压缩应用于该流。我希望我能写出这样的东西:
using (var compressedStream = new GZipStream(rawStream, CompressionLevel.Fastest))
{
await UploadFile(compressedStream);
}
但是这不起作用,因为stream
的{{1}}参数是一个输出流,所以它的方法是错误的。
问题
如何将原始流包装在压缩流中,仍然让我的消费功能将数据从流中拉出来?
注意
上面的例子实际上是简单的,因为我还需要应用base64编码。所以我真正想要的是这样的:
GZipStream
但我想如果有人能向我解释压缩部分是如何工作的,我可以弄清楚如何实现整个链。
答案 0 :(得分:0)
您需要第三个流来GZipStream
写入:
private static async Task<MemoryStream> CompressStream(Stream inputStream)
{
var outputStream = new MemoryStream();
Console.WriteLine(inputStream.Length);
using (var gzipStream = new GZipStream(outputStream, CompressionLevel.Fastest, leaveOpen: true))
{
await inputStream.CopyToAsync(gzipStream);
}
Console.WriteLine(outputStream.Length);
return outputStream;
}
答案 1 :(得分:0)
到目前为止,所提出的方法都没有让我信服。所以我继续编写了以下自定义流,允许从流中提取GZipped和Base64编码数据。
我做了一些测试,似乎工作正常。
我认为这种模式在其他环境中也很有用,可以将“推送管道”转变为“拉动管道”。
public sealed class GzipBase64Stream : Stream
{
#region constructor / cleanup
public GzipBase64Stream(Stream inputStream)
{
try
{
InputStream = inputStream;
ToBase64Transform = new ToBase64Transform();
OutputStream = new MemoryStream();
Base64Stream = new CryptoStream(OutputStream, ToBase64Transform, CryptoStreamMode.Write);
GzipStream = new GZipStream(Base64Stream, CompressionLevel.Fastest, true);
}
catch
{
Cleanup();
throw;
}
}
private void Cleanup()
{
GzipStream?.Dispose();
Base64Stream?.Dispose();
OutputStream?.Dispose();
ToBase64Transform?.Dispose();
InputStream?.Dispose();
}
#endregion
#region private variables
private bool EndOfInputStreamReached = false;
private readonly Stream InputStream;
private readonly ToBase64Transform ToBase64Transform;
private readonly MemoryStream OutputStream;
private readonly CryptoStream Base64Stream;
private readonly GZipStream GzipStream;
#endregion
#region stream overrides
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => 0;
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void SetLength(long value) => throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override void Flush() => throw new NotSupportedException();
public override int Read(byte[] buffer, int offset, int count)
{
while ((OutputStream.Position >= (OutputStream.Length - 1)) && !EndOfInputStreamReached)
{
// No unread data available in the output buffer
// -> release memory of output buffer and read new data from the source and feed through the pipeline
OutputStream.SetLength(0);
var inputBuffer = new byte[1024];
var readCount = InputStream.Read(inputBuffer, 0, inputBuffer.Length);
if (readCount == 0)
{
EndOfInputStreamReached = true;
GzipStream.Flush();
GzipStream.Dispose(); // because Flush() does not actually flush...
}
else
{
GzipStream.Write(inputBuffer, 0, readCount);
}
OutputStream.Position = 0;
}
return OutputStream.Read(buffer, offset, count);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
Cleanup();
}
#endregion
}