【问题标题】:Can I get a GZipStream for a file without writing to intermediate temporary storage?我可以在不写入中间临时存储的情况下获取文件的 GZipStream 吗?
【发布时间】:2026-01-18 05:35:01
【问题描述】:

我能否在不将整个压缩内容写入临时存储的情况下为磁盘上的文件获取GZipStream?我目前正在磁盘上使用一个临时文件,以避免在非常大的文件上使用MemoryStream 可能导致内存耗尽(这工作正常)。

    public void UploadFile(string filename)
    {
        using (var temporaryFileStream = File.Open("tempfile.tmp", FileMode.CreateNew, FileAccess.ReadWrite))
        {   
            using (var fileStream = File.OpenRead(filename))
            using (var compressedStream = new GZipStream(temporaryFileStream, CompressionMode.Compress, true))
            {
                fileStream.CopyTo(compressedStream);
            }

            temporaryFileStream.Position = 0;

            Uploader.Upload(temporaryFileStream);
        }
    }

我想做的是通过创建GZipStream 来消除临时存储空间,并且仅在 Uploader 类向其请求字节时才从原始文件中读取它。这样的事情可能吗?如何构建这样的实现?

注意Upload 是一个带有签名static void Upload(Stream stream) 的静态方法。

编辑:如果有用的话,完整的代码是here。不过,我希望我在上面的示例中包含了所有相关的上下文。

【问题讨论】:

  • 如何上传?你使用 NetworkStream 吗?还是您使用 HttpWebClient?还是别的什么?
  • @elgonzo 我使用 AWS 开发工具包 TransferUtilityUploadRequest。如果有用的话,完整的代码是here,尽管我希望它不是。我无法控制使用Stream 的代码,我只需为其提供Stream 对象。
  • 您需要将 GZipStream 构造函数中的temporaryFileStream 替换为要上传到的流。
  • @elgonzo 不太好用,因为 GZipStream 在写入之前不包含字节。我想在完成写入之前从压缩流中开始读取,并且我不想在执行此操作时将压缩流的全部内容保存在磁盘或内存中。
  • @Boinst,对不起,我错了。没有注意到 GZipStream 不能作为过滤流来读取未压缩的文件并吐出压缩数据... Doh...

标签: c# gzipstream


【解决方案1】:

是的,这是可能的,但对于任何标准的 .NET 流类来说都不容易。当我需要做这样的事情时,我创建了一个new type of stream

它基本上是一个循环缓冲区,允许一个生产者(写入者)和一个消费者(读取者)。它很容易使用。让我举个例子。同时,您可以修改文章中的示例。

稍后:这是一个应该接近您要求的示例。

using (var pcStream = new ProducerConsumerStream(BufferSize))
{
    // start upload in a thread
    var uploadThread = new Thread(UploadThreadProc(pcStream));
    uploadThread.Start();

    // Open the input file and attach the gzip stream to the pcStream
    using (var inputFile = File.OpenRead("inputFilename"))
    {
        // create gzip stream
        using (var gz = new GZipStream(pcStream, CompressionMode.Compress, true))
        {
            var bytesRead = 0;
            var buff = new byte[65536]; // 64K buffer
            while ((bytesRead = inputFile.Read(buff, 0, buff.Length)) != 0)
            {
                gz.Write(buff, 0, bytesRead);
            }
        }
    }
    // The entire file has been compressed and copied to the buffer.
    // Mark the stream as "input complete".
    pcStream.CompleteAdding();

    // wait for the upload thread to complete.
    uploadThread.Join();

    // It's very important that you don't close the pcStream before
    // the uploader is done!
}

上传线程应该很简单:

void UploadThreadProc(object state)
{
    var pcStream = (ProducerConsumerStream)state;
    Uploader.Upload(pcStream);
}

当然,您可以将生产者放在后台线程上,然后在主线程上完成上传。或者让它们都在后台线程上。我不熟悉你的上传者的语义,所以我会把这个决定留给你。

【讨论】:

  • 谢谢吉姆,这看起来很理想,我现在正在尝试。
  • 这个解决方案对我来说非常有效,不需要对 Jim 提供的代码进行真正的更改。谢谢吉姆!
最近更新 更多