You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Blob解压大Zip文件且不将全部内容加载到内存?

从Azure Blob解压大Zip文件避免内存溢出的解决方案

原代码中,Azure Blob的OpenReadAsync返回的是不可查找的网络流,而ZipArchive要求流支持随机访问(CanSeek = true),因此它会自动将整个流复制到内存中的MemoryStream,导致处理4G级大文件时出现内存溢出异常。以下是两种可行的解决方案:


方案1:先将Blob下载到本地临时文件再解压

这种方式将Blob数据存储在本地磁盘而非内存,内存占用极低,是处理超大文件的首选方案。

// 下载Blob到本地临时文件
string tempFilePath = Path.GetTempFileName();
await blobClient.DownloadToAsync(tempFilePath);

// 基于本地文件流创建ZipArchive(文件流原生支持Seek,无需加载全量数据到内存)
using (FileStream fileStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read))
using (ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
    foreach (ZipArchiveEntry entry in zipArchive.Entries)
    {
        var destinationFilePath = $@"c:\temp\Unzipfiles-zip\{entry.FullName}";
        // 提前创建目标文件的父目录,避免路径不存在异常
        Directory.CreateDirectory(Path.GetDirectoryName(destinationFilePath));
        
        using (Stream entryStream = entry.Open())
        using (FileStream targetFileStream = new FileStream(destinationFilePath, FileMode.Create))
        {
            await entryStream.CopyToAsync(targetFileStream).ConfigureAwait(false);
        }
    }
}

// 解压完成后清理临时文件
File.Delete(tempFilePath);

方案2:使用自定义可查找Blob流(无需完整下载)

如果对磁盘占用有严格要求,可以通过自定义流包装器,利用Azure Blob的范围读取能力实现可查找流,避免完整下载Blob。

自定义可查找Blob流实现

public class SeekableBlobStream : Stream
{
    private readonly BlobClient _blobClient;
    private readonly long _blobTotalLength;
    private long _currentPosition;

    public SeekableBlobStream(BlobClient blobClient)
    {
        _blobClient = blobClient;
        _blobTotalLength = _blobClient.GetProperties().Value.ContentLength;
        _currentPosition = 0;
    }

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => _blobTotalLength;
    public override long Position
    {
        get => _currentPosition;
        set => _currentPosition = Math.Clamp(value, 0, _blobTotalLength);
    }

    public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        if (_currentPosition >= _blobTotalLength)
            return 0;

        long readEndPosition = Math.Min(_currentPosition + count, _blobTotalLength);
        using (var tempStream = new MemoryStream())
        {
            await _blobClient.DownloadRangeToAsync(tempStream, new HttpRange(_currentPosition, readEndPosition - _currentPosition), cancellationToken: cancellationToken);
            tempStream.Position = 0;
            int bytesRead = await tempStream.ReadAsync(buffer, offset, count, cancellationToken);
            _currentPosition += bytesRead;
            return bytesRead;
        }
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                _currentPosition = offset;
                break;
            case SeekOrigin.Current:
                _currentPosition += offset;
                break;
            case SeekOrigin.End:
                _currentPosition = _blobTotalLength + offset;
                break;
        }
        _currentPosition = Math.Clamp(_currentPosition, 0, _blobTotalLength);
        return _currentPosition;
    }

    public override void Flush() => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}

使用自定义流的解压代码

using (var seekableStream = new SeekableBlobStream(blobClient))
using (ZipArchive zipArchive = new ZipArchive(seekableStream, ZipArchiveMode.Read))
{
    foreach (ZipArchiveEntry entry in zipArchive.Entries)
    {
        var destinationFilePath = $@"c:\temp\Unzipfiles-zip\{entry.FullName}";
        Directory.CreateDirectory(Path.GetDirectoryName(destinationFilePath));
        
        using (Stream entryStream = entry.Open())
        using (FileStream targetFileStream = new FileStream(destinationFilePath, FileMode.Create))
        {
            await entryStream.CopyToAsync(targetFileStream).ConfigureAwait(false);
        }
    }
}

注意:该方案会增加Blob的请求次数,需要在内存占用和请求性能之间做权衡。


内容的提问来源于stack exchange,提问作者Youxu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:22:53