.NET 4.5 起加入 ZipArchive、ZipFile 等列類別,自此不用額外安裝第三方程式庫就能製作 ZIP 檔。微軟官方文件則有篇範例文章,操作說明:壓縮與解壓縮檔案 - Microsoft Docs,介紹如何使用 System.IO.Compression 的一系列類別壓縮及解壓縮檔案。

我遇到一個需求,要將使用者在網站查詢的結果,以一筆資料一個檔案形式匯出,再集結壓縮成單一 ZIP 檔方便使用者下載。爬文找到的範例多以檔案形式處理為主,而我想省掉將資料寫成檔案再壓縮的步驟,但直接將記憶體 byte[] 壓成 ZIP(也是 byte[])的完整範例不多,索性將摸索成果整理成筆記:如何將記憶體中的 byte[] 直接壓成 ZIP 保存於記憶體?如此,直接 Response.BinaryWrite() 即可下載,全程資料不落地(不寫暫存檔),有利減少IO、提升效能。(註:前題是處理資料量不構成伺服器記憶體的壓力)

完整程式範例如下:

    class Program
    {
        static void Main(string[] args)
        {
            var src = new Dictionary<string, byte[]>()
            {
                ["name.txt"] = Encoding.UTF8.GetBytes("Jeffrey"),
                ["score.txt"] = Encoding.UTF8.GetBytes("32767")
            };
            var zip = ZipHelper.ZipData(src);
            System.IO.File.WriteAllBytes("test.zip", zip);
            var res = ZipHelper.UnzipData(zip);
            foreach (var fileName in res.Keys)
            {
                Console.WriteLine($"FileName={fileName}");
                Console.WriteLine($"Content={Encoding.UTF8.GetString(res[fileName])}");
            }
            Console.Read();
        }
    }

    public class ZipHelper
    {
        public static byte[] ZipData(Dictionary<string, byte[]> data)
        {
            using (var zipStream = new MemoryStream())
            {
                using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Update))
                {
                    foreach (var fileName in data.Keys)
                    {
                        var entry = zipArchive.CreateEntry(fileName);
                        using (var entryStream = entry.Open())
                        {
                            var buff = data[fileName];
                            entryStream.Write(buff, 0, buff.Length);
                        }
                    }
                }
                return zipStream.ToArray();
            }
        }

        public static Dictionary<string, byte[]> UnzipData(byte[] zip)
        {
            var dict = new Dictionary<string, byte[]>();
            using (var msZip = new MemoryStream(zip))
            {
                using (var archive = new ZipArchive(msZip, ZipArchiveMode.Read))
                {
                    archive.Entries.ToList().ForEach(entry =>
                    {
                        //e.FullName可取得完整路徑
                        if (string.IsNullOrEmpty(entry.Name)) return;
                        using (var entryStream = entry.Open())
                        {
                            using (var msEntry = new MemoryStream())
                            {
                                entryStream.CopyTo(msEntry);
                                dict.Add(entry.Name, msEntry.ToArray());
                            }
                        }
                    });
                }
            }
            return dict;
        }
    }

實測將 Dictionary<string, byte[]> 壓成 ZIP,解壓後內容還原無誤:

將壓縮結果另存 ZIP 檔,使用 7-Zip 可正常檢視解壓,測試成功!

Sample code of compression and decompression data on-the-fly.(withoutr temp file)


Comments

# by TuTu

有辦法不使用 Memory ?? 檔案過大 容易造成例外

# by Jeffrey

to TuTu,若資料量超過記憶體限制,可回歸用暫存檔案到磁碟的做法,並確保處理完刪除檔案(即使失敗也要)。

Post a comment