專案需求:將任意檔案/目錄壓縮為 ZIP,或解壓縮 ZIP 至指定資料夾。選用開源 **ICSharpCode.SharpZipLib**,僅需引用單一 DLL,無外部相依,適合 .NET Framework / .NET Core 專案。
dotnet add package SharpZipLib
// 或
Install-Package SharpZipLib
ZipHelper (整合原文 ZipClass/ZipFileMain)
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Checksums;
public static class ZipHelper
{
/// 單一檔案壓縮
public static void CompressFile(string sourceFile, string destZip, int compressionLevel = 6, int bufferSize = 4096)
{
if (!File.Exists(sourceFile)) throw new FileNotFoundException("來源檔案不存在", sourceFile);
using (var fsIn = File.OpenRead(sourceFile))
using (var fsOut = File.Create(destZip))
using (var zipStream = new ZipOutputStream(fsOut))
{
zipStream.SetLevel(compressionLevel); // 0=Store, 9=BestCompression
var entry = new ZipEntry(Path.GetFileName(sourceFile)) { DateTime = DateTime.Now };
zipStream.PutNextEntry(entry);
byte[] buffer = new byte[bufferSize];
int read;
while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
zipStream.Write(buffer, 0, read);
}
}
/// 目錄壓縮 (保留相對路徑)
public static void CompressDirectory(string sourceDir, string destZip, int compressionLevel = 6, string password = null)
{
if (!Directory.Exists(sourceDir)) throw new DirectoryNotFoundException("來源目錄不存在");
using (var fsOut = File.Create(destZip))
using (var zipStream = new ZipOutputStream(fsOut))
{
zipStream.SetLevel(compressionLevel);
if (!string.IsNullOrEmpty(password)) zipStream.Password = password;
var files = Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var relPath = file.Substring(sourceDir.Length).TrimStart(Path.DirectorySeparatorChar);
var entry = new ZipEntry(relPath) { DateTime = File.GetLastWriteTime(file) };
zipStream.PutNextEntry(entry);
using (var fsIn = File.OpenRead(file))
{
byte[] buffer = new byte[4096];
int read;
while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
zipStream.Write(buffer, 0, read);
}
}
}
}
/// 進階:自訂 CRC/Size 寫入 Header (相容某些解壓工具)
public static void CompressWithHeaderInfo(string sourceFile, string destZip)
{
using (var fsIn = File.OpenRead(sourceFile))
using (var fsOut = File.Create(destZip))
using (var zipStream = new ZipOutputStream(fsOut))
{
zipStream.SetLevel(6);
var crc = new Crc32();
byte[] buffer = new byte[fsIn.Length];
fsIn.Read(buffer, 0, buffer.Length);
crc.Reset(); crc.Update(buffer);
var entry = new ZipEntry(Path.GetFileName(sourceFile))
{
DateTime = DateTime.Now,
Size = fsIn.Length,
Crc = crc.Value
};
zipStream.PutNextEntry(entry);
zipStream.Write(buffer, 0, buffer.Length);
}
}
}
UnZipHelper
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
public static class UnZipHelper
{
/// 解壓縮 ZIP 至目標目錄
public static void Extract(string zipFile, string destDir, string password = null, bool overwrite = true)
{
if (!File.Exists(zipFile)) throw new FileNotFoundException("ZIP 檔案不存在", zipFile);
Directory.CreateDirectory(destDir);
using (var fs = File.OpenRead(zipFile))
using (var zipStream = new ZipInputStream(fs))
{
if (!string.IsNullOrEmpty(password)) zipStream.Password = password;
ZipEntry entry;
while ((entry = zipStream.GetNextEntry()) != null)
{
if (entry.IsDirectory) continue; // 目錄由檔案建立時自動建立
string targetPath = Path.Combine(destDir, entry.Name);
string targetDir = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(targetDir)) Directory.CreateDirectory(targetDir);
if (File.Exists(targetPath) && !overwrite) continue;
using (var fsOut = File.Create(targetPath))
{
byte[] buffer = new byte[4096];
int read;
while ((read = zipStream.Read(buffer, 0, buffer.Length)) > 0)
fsOut.Write(buffer, 0, read);
}
File.SetLastWriteTime(targetPath, entry.DateTime);
}
}
}
/// 列舉 ZIP 內容 (不解壓)
public static IEnumerable ListEntries(string zipFile)
{
using (var fs = File.OpenRead(zipFile))
using (var zipFileObj = new ZipFile(fs))
{
foreach (ZipEntry entry in zipFileObj)
yield return entry;
}
}
}
// 壓縮單一檔案
ZipHelper.CompressFile(@"C:\Data\report.pdf", @"C:\Backup\report.zip", compressionLevel: 9);
// 壓縮整個資料夾 (含子目錄)
ZipHelper.CompressDirectory(@"C:\Projects\MyApp", @"C:\Backup\MyApp.zip", password: "secret123");
// 解壓縮
UnZipHelper.Extract(@"C:\Backup\MyApp.zip", @"C:\Restore\MyApp", password: "secret123");
// 僅列出內容
foreach (var e in UnZipHelper.ListEntries(@"C:\Backup\MyApp.zip"))
Console.WriteLine($"{e.Name,-40} {e.Size,10} bytes {e.DateTime:yyyy-MM-dd HH:mm} CRC:{e.Crc:X8}");
| 問題 | 原因 | 解決 |
|---|---|---|
| 中文檔名亂碼 | SharpZipLib 預設用 UTF-8,舊版 Windows 解壓工具預期 Big5/CP950 | 設定 zipStream.UseZip64 = UseZip64.Off 或改用 ZipFile 類並指定 Encoding |
| 大於 4GB 檔案失敗 | 傳統 ZIP 32-bit 限制 | 啟用 UseZip64.On (預設自動),確保目標解壓工具支援 ZIP64 |
| 資料流未關閉導致檔案鎖定 | 忘記 Dispose | 全程使用 using 區塊 |
| 密碼保護但仍可列出檔名 | ZIP 加密僅加密資料,不加密 Header | 若需隱藏檔名,先打包為無名 tar 再加密,或改用 7z/AES-256 |
Parallel.ForEach,但單一大檔無法平行 (ZIP 串流式)。