代码之家  ›  专栏  ›  技术社区  ›  Robert Hegner

对提取数据的方法所使用的流应用GZip压缩

  •  1
  • Robert Hegner  · 技术社区  · 7 年前

    先决条件

    我有一条小溪 rawStream 还有一个方法,它获取一个流并将其读取到最后,我们可以这样说:

    Task UploadFile(Stream stream) { ... }
    

    目前,此方法已成功使用,如下所示:

    await UploadFile(rawStream);
    

    我想做什么

    现在我需要对该流应用GZip压缩。我希望我能写这样的东西:

    using (var compressedStream = new GZipStream(rawStream, CompressionLevel.Fastest))
    {
        await UploadFile(compressedStream);
    }
    

    但这不起作用,因为 stream 的参数 GZipStream 是一个输出流,因此它是错误的。

    问题

    如何将原始流封装在压缩流中,同时仍让消费函数从流中提取数据?


    笔记

    上面的示例实际上简化了,因为我还需要应用base64编码。所以我真正想要的是这样的:

    using (var compressedStream = new GZipStream(rawStream, CompressionLevel.Fastest))
    using (var encodedStream = new CryptoStream(compressedStream, new ToBase64Transform(), CryptoStreamMode.Read))
    {
        await UploadFile(encodedStream);
    }
    

    但我想,如果有人能向我解释一下压缩部分是如何工作的,我就能知道如何实现整个链。

    2 回复  |  直到 7 年前
        1
  •  1
  •   Robert Hegner    7 年前

    到目前为止,没有一种方法能真正说服我。所以我继续写了下面的定制流,它允许从流中提取gzip和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
    
    }
    
        2
  •  0
  •   ejohnson    7 年前

    您需要第三条流 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;
    }