如何使用 .NET Framework 內建的 zip 壓縮功能

長久以來,我們在 C# 裡需要使用 Zip 功能時,常常我們所想到的都仿間常見的 Zip Library ,如:ICSarpCode.SharpZipLib、DotNetZip 等.. 其實 .NET Framework 的 System.IO.Compression 命名空間裡就有提供 Zip 的壓縮功能。

前言

長久以來,我們在 C# 裡需要使用 Zip 功能時,常常我們所想到的都仿間常見的 Zip Library ,如:ICSarpCode.SharpZipLib、DotNetZip 等.. 其實 .NET Framework 的 System.IO.Compression 命名空間裡就有提供 Zip 的壓縮功能

 

實作
一、建立 ZipArchive

在 System.IO.Compression 名命空間裡提供一個類別 ZipArchive 類別可以您建立一個 Zip 壓縮檔案的執行個體。我們先來看看建立 ZipArchive 物件時所需要參數

在初始化 ZipArchive 時,最多可以傳入三個參數

Stream:

同樣的,在 .NET Framework 裡面,對於檔案的處理都是透過 FileStream 

ZipAcrchiveMode:

這個參數提供三種設定的方式,如下:

  1. Create :都用於建立新的 Zip 壓縮檔案的時候
  2. Read:用於讀取現有的 Zip 壓縮檔案 (這個時候不提供改變壓縮檔案內容的功能,唯讀處理)
  3. Update:用於更新現有 Zip 壓縮檔案時使用

leaveOpen:

是否在建立 ZipArchive 後維持資料流開啟狀態

程式碼如下:

var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true);
注意:這裡使用的的 FileStream 會有兩個,一個是要建立 ZipArchive 的 FileStream,另一個是要壓縮來源的檔案的 FileStream,不要搞混了!

 

二、將要壓縮的實際檔案讀入 FileStream 中,並轉換為 byte[]

這邊的傳換要注意 Stream 類別中讀取為 byte 時的 offset 為 int ,我們知道 Int 為帶正負號的 32位元整數,當您壓縮的超大檔案可能長度限制問題,這邊筆者另外寫一個  BinaryReadToEnd() 方法來處理這件事情。

程式碼如下:

byte[] xlsxBytes = BinaryUtil.BinaryReadToEnd(f);

BinaryReadToEnd 的程式碼如下,我先讀入一個 buffer 中,所以在大的檔案都讀得進來,只要你的 RAM 夠大:

        public static byte[] BinaryReadToEnd(Stream stream)
        {
            long originalPosition = 0;

            if (stream.CanSeek)
            {
                originalPosition = stream.Position;
                stream.Position = 0;
            }

            try
            {
                byte[] readBuffer = new byte[4096];

                int totalBytesRead = 0;
                int bytesRead;

                while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
                {
                    totalBytesRead += bytesRead;

                    if (totalBytesRead == readBuffer.Length)
                    {
                        int nextByte = stream.ReadByte();
                        if (nextByte != -1)
                        {
                            byte[] temp = new byte[readBuffer.Length * 2];
                            Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
                            Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
                            readBuffer = temp;
                            totalBytesRead++;
                        }
                    }
                }

                byte[] buffer = readBuffer;
                if (readBuffer.Length != totalBytesRead)
                {
                    buffer = new byte[totalBytesRead];
                    Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
                }
                return buffer;
            }
            finally
            {
                if (stream.CanSeek)
                {
                    stream.Position = originalPosition;
                }
            }
        }

 

三、在 ZipArchive 中建立操作的進入點

當我們取到 byte[] 後,接著就是在 Zip 檔案哩,建立它的名稱,你可以當作進入點,這個進入點你可以看就是當你使用 WinZip 或 7-Zip 的壓縮軟體開啟時,在壓縮軟體裡,滑鼠可以點選的那個檔案名稱。

var zipArchiveEntry = archive.CreateEntry(createEntryFileName, CompressionLevel.Fastest);

 

四、將 byte[] 寫入 zipArchiveEntry 中

這邊我們必須將 zipArchiveEntry 開啟,我們才可以將 byte[] 寫入該 zipArchiveEntry 中,程式碼如下:

using (var zipStream = zipArchiveEntry.Open())
{
      zipStream.Write(xlsxBytes, 0, xlsxBytes.Length);
}
提醒:如果要壓縮多個檔案,請重複 三、四 步驟動作

完成後,記得將 FileStream 做 Close 的處理。

完整的「壓縮」程式碼如下 (這邊的範例是寫成一個方法 Method):

public static string CreateZipArchiveByExcel(string sourceFile)
{
    string workPath = Path.GetDirectoryName(sourceFile);
    string createEntryFileName = Path.GetFileName(sourceFile);
    string targetZipFileName = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(sourceFile), ".zip");
    string distinationFile = Path.Combine(workPath, targetZipFileName);

    FileStream f = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);

    try
    {
        using (var fileStream = new FileStream(distinationFile, FileMode.CreateNew))
        {
            using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true))
            {
                byte[] xlsxBytes = BinaryUtil.BinaryReadToEnd(f);

                var zipArchiveEntry = archive.CreateEntry(createEntryFileName, CompressionLevel.Fastest);
                BinaryReader bs = new BinaryReader(f);

                using (var zipStream = zipArchiveEntry.Open())
                {
                    zipStream.Write(xlsxBytes, 0, xlsxBytes.Length);
                }
            }
        }
    }
    finally
    {
        f.Close();
    }

    return distinationFile;
}

 

結語:

有時筆者在替客戶撰寫 Console 的排程程式時,在部署時,因為客戶端環境的關係,一個 Console 的 EXE 檔案還得參照第三方的 Lib 時不免有點麻煩,如果 .NET Framework 即內建就盡量使用內建功能來達到需求。

提供各位參考。

 


 

簽名:

學習是一趟奇妙的旅程

這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。

軟體開發之路(FB 社團)https://www.facebook.com/groups/361804473860062/

Gelis 程式設計訓練營(粉絲團)https://www.facebook.com/gelis.dev.learning/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^