首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 線上壓縮解壓縮實戰:SharpZipLib 串流處理大檔案不落地

     編輯:關於C語言

Web API、雲端函式、容器化服務常面臨「不寫入暫存檔、直接在記憶體/網路串流間壓縮解壓」的需求。SharpZipLib 完整支援 Stream 操作,配合 HttpClient、MinIO、Azure Blob 等物件儲存 SDK,即可實現零落地、低記憶體、高併發的線上壓縮解壓縮管線。本文示範三大核心場景:上傳即壓縮、下載即解壓、多檔案打包下載。

核心原則:Stream.CopyTo 為王

  • 所有 SharpZipLib *Stream 類別(ZipOutputStream、GZipOutputStream、ZipInputStream...)皆繼承 Stream。
  • 使用 await source.CopyToAsync(dest, bufferSize, cancellationToken),不載入完整檔案到記憶體,背壓自動調節。
  • 緩衝區大小建議 81920 (80KB),平衡系統呼叫與記憶體佔用。

場景一:HTTP 上傳 → 即時 GZip/Zip 壓縮 → 存入物件儲存

[HttpPost("upload-compress")]
[RequestSizeLimit(long.MaxValue)] // 關閉 Kestrel 預設 30MB 限制
public async Task UploadAndCompress(
    IFormFile file, 
    [FromServices] IObjectStorage storage,
    CancellationToken ct)
{
    // 1. 開啟上游輸出串流(MinIO / S3 / Azure Blob / 本地檔案)
    await using Stream destStream = await storage.OpenWriteAsync($"archive/{file.FileName}.gz", ct);

    // 2. 包裝 GZip 壓縮串流
    await using var gzStream = new GZipOutputStream(destStream) { IsStreamOwner = false };
    gzStream.SetLevel(6); // 可選

    // 3. 直接從請求串流複製到壓縮串流
    await using Stream srcStream = file.OpenReadStream();
    await srcStream.CopyToAsync(gzStream, 81920, ct);

    // 4. 確保壓縮資料刷新到底層
    await gzStream.FlushAsync(ct);

    return Ok(new { message = "Compressed & stored", originalSize = file.Length });
}

關鍵:IsStreamOwner = false 讓 GZipOutputStream.Dispose() 不關閉 物件儲存串流,由外層 using 統一管理。

場景二:物件儲存 → 即時解壓 → HTTP 下載(串流回應)

[HttpGet("download-decompress/{objectKey}")]
public async Task DecompressAndDownload(string objectKey, CancellationToken ct)
{
    // 1. 取得上游壓縮串流
    await using Stream srcStream = await _storage.OpenReadAsync(objectKey, ct);

    // 2. 設定回應標頭
    string fileName = Path.GetFileNameWithoutExtension(objectKey); // 移除 .gz
    Response.Headers.ContentDisposition = $"attachment; filename=\"{fileName}\"";
    Response.Headers.ContentType = "application/octet-stream";

    // 3. 包裝解壓縮串流 → 直接複製到 HTTP 回應 Body
    await using var gzIn = new GZipInputStream(srcStream);
    await gzIn.CopyToAsync(Response.Body, 81920, ct);
}

此模式適用於任何 Stream 來源:FileStream、MemoryStream、S3Object.ResponseStream、BlobClient.OpenRead()。

場景三:多檔案打包成 Zip 串流下載(不建立暫存 Zip)

[HttpGet("download-zip")]
public async Task DownloadMultipleAsZip([FromQuery] string[] keys, CancellationToken ct)
{
    Response.Headers.ContentDisposition = "attachment; filename=\"batch.zip\"";
    Response.Headers.ContentType = "application/zip";

    await using var zipOut = new ZipOutputStream(Response.Body) { IsStreamOwner = false };
    zipOut.SetLevel(3); // 快速壓縮,降低 CPU

    foreach (string key in keys)
    {
        // 逐個讀取物件並寫入 Zip Entry
        await using Stream objStream = await _storage.OpenReadAsync(key, ct);
        var entry = new ZipEntry(Path.GetFileName(key))
        {
            DateTime = DateTime.UtcNow,
            Size = objStream.CanSeek ? objStream.Length : -1
        };
        zipOut.PutNextEntry(entry);
        await objStream.CopyToAsync(zipOut, 81920, ct);
        zipOut.CloseEntry();
    }
    await zipOut.FinishAsync(ct); // 寫入中央目錄結構
}

優勢:記憶體佔用恆定 ≈ 緩衝區大小 × 併發數,無論打包多少 GB 檔案都不會 OOM。

進階:非同步列舉器逐筆產出 Zip Entry(.NET 6+ IAsyncEnumerable)

public async IAsyncEnumerable CreateZipEntriesAsync(
    IEnumerable keys, 
    [EnumeratorCancellation] CancellationToken ct)
{
    foreach (string key in keys)
    {
        ct.ThrowIfCancellationRequested();
        await using Stream s = await _storage.OpenReadAsync(key, ct);
        yield return new ZipEntry(Path.GetFileName(key))
        {
            DateTime = DateTime.UtcNow,
            Size = s.CanSeek ? s.Length : -1
        };
        // 注意:這裡只產生 Entry metadata,實際資料複製在呼叫端
    }
}

// 呼叫端
await using var zipOut = new ZipOutputStream(Response.Body) { IsStreamOwner = false };
await foreach (var entry in CreateZipEntriesAsync(keys, ct))
{
    zipOut.PutNextEntry(entry);
    await using Stream s = await _storage.OpenReadAsync(entry.Name, ct);
    await s.CopyToAsync(zipOut, 81920, ct);
    zipOut.CloseEntry();
}
await zipOut.FinishAsync(ct);

效能與資源管理檢核表

項目最佳實踐
緩衝區81920 bytes (80KB),避免 LOH、減少系統呼叫
IsStreamOwner外層串流由呼叫端管理 → false;內層暫存串流 → true
Flush/FinishGZipOutputStream.FlushAsync、ZipOutputStream.FinishAsync 必呼叫
CancellationToken全程透傳,支援客戶端中斷釋放資源
Seek 不支援物件儲存串流通常不可 Seek,ZipEntry.Size 設 -1,SharpZipLib 自動處理
大檔案 Zip64zipOut.UseZip64 = UseZip64.On 支援 4GB+ Entry
例外處理包裝 try/finally 確保串流 Dispose,避免連線洩漏

替代方案快速比較

庫優點缺點適用場景
SharpZipLib零相依、支援 Stream、Zip/GZip/BZip2/Tar、.NET Standard 2.0API 較舊、文件稀少跨平台、容器化、輕量微服務
System.IO.Compression內建、ZipArchive 支援 Stream無 BZip2/Tar、無 AES 加密(舊版)、ZipArchive 需可 Seek 串流寫入中央目錄簡單 Zip、.NET 6+ 專案
ZstdSharp / Brotli壓縮率/速度更優非標準格式、需雙端支援內部高效能傳輸、日誌壓縮

掌握「串流包裝串流、CopyToAsync 穿透、IsStreamOwner 控制權」三大核心模式,即能在任何 .NET 服務中構建高效能、零暫存檔、可水平擴展的線上壓縮解壓縮管線,滿足現代雲原生架構的嚴苛資源限制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved