2012年1月7日 星期六

壓縮資料流

認識 Compress Stream

.NET Framework 提供二個類別,用來處理壓縮資料。

  • GZIP:使用 GZipStream 類別表示 GZIP 資料格式,它無失真檔案壓縮和解壓縮的工業標準演算法。
  • Deflate:使用 DeflateStream 類別表示結實 (Deflate) 演算法,該演算法是無失真檔案壓縮和解壓縮的工業標準演算法。它使用 LZ77 演算法和 Huffman編碼的組合。

關於這二個壓縮物件

  1. 最大只能對4GB的資料進行壓縮。
  2. GZIP 壓縮後的資料包含一些檔頭資訊,Deflate 則無。
  3. GZipStream 類別使用 GZIP 資料格式,這種格式包含用於偵測資料損毀的循環冗餘檢查值 (Cyclic Redundancy Check value, CRC)。
  4. 若要自用的話,使用 DeflateStream 方法會壓的小一些;若要分發出去給其他人使用,則使用 GZipStream

.Net4.0已經沒有4G的限制了,另外在 Stream 類別中提供新的 CopyTo 方法,可快速方便複製目前資料流的內容到另一個資料流。

如何壓縮資料流

一般資料流執行寫入動作時,會將資料輸出到某個資源檔中。 例如: FileStream 會輸出到檔案,而 MemoryStream 則是輸出到記憶體。 而壓縮資料流則是輸出另一個資料流 (FileStream、MemoryStrem...)。 所以,當建立 GZipStream 的執行個體時,必須提供一個 Stream 當參數,然後再呼叫執行個體的 GZipStream.Write 方法,資料就會寫入到該 Stream

GZipStream 建構子:

//參數 stream
//壓縮時,做為輸出的資料流
//解壓縮時,做為來源的資料流
public GZipStream(Stream stream, CompressionMode mode);                     
public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen);     //leaveOpen : true 表示在處置 GZipStream 物件之後,將資料流保持開啟,否則為 false。

GZipStream.Write

Writes compressed bytes to the underlying stream from the specified byte array.

public override void Write(
	byte[] array,   //將 array 中的資料,壓縮之後,寫到執行個體指定的那個 stream。
	int offset,
	int count
)

GZipStream.Read

Reads a number of decompressed bytes into the specified byte array.

public override int Read(
	byte[] array,   //由執行個體指定的那個 stream 讀取資料,解壓縮後,寫到 array 陣列。
	int offset,
	int count
)

壓縮資料的簡單步驟:

  • 建立一個輸出資料流 Stream ,用來存放壓縮後的資料。
  • 建立 GZipStream 物件。指定輸出資料流,並將 CompressionMode 設定為 Compress
  • 讀取資料。
  • 將讀取的資料,透過 GZipStream.Write 方法,執行壓縮,並將壓縮過的資料寫入到 Stream
public static void GZipCompress1(string inFilename)
{
	// 建立一個輸出資料流 Stream ,用來存放壓縮後的資料。
	string outFilename = inFilename + ".gz";
	FileStream fs_Dest = File.Create(outFilename);

	// 建立 GZipStream 物件。指定輸出資料流,並將 CompressionMode 設為 Compress。
	GZipStream gzipStream = new GZipStream(fs_Dest, CompressionMode.Compress);

	// 讀取來源檔的資料
	FileStream fs_Source = File.OpenRead(inFilename);
	const int buf_size = 4096;
	byte[] buffer = new byte[buf_size];
	int bytes_read = 0;
	do
	{
		bytes_read = fs_Source.Read(buffer, 0, buf_size);
		//寫入壓縮資料流 compStream
		gzipStream.Write(buffer, 0, bytes_read);        
	} while (bytes_read != 0);

	gzipStream.Close();
	fs_Dest.Close();
	fs_Source.Close();
}
public static void GZipCompress2(string inFilename)
{
	// 建立一個輸出資料流 Stream ,用來存放壓縮後的資料。
	string outFilename = inFilename + ".gz";
	FileStream fs_Dest = File.Create(outFilename);

	// 建立 GZipStream 物件。指定輸出資料流,並將 CompressionMode 設為 Compress。
	GZipStream compStream = new GZipStream(fs_Dest, CompressionMode.Compress);

	// 讀取來源檔的資料
	FileStream fs_Source = File.OpenRead(inFilename);

	// 將資料由 filestream CopyTo GZipStream 完成壓縮
	fs_Source.CopyTo(compStream);

	compStream.Close();
	fs_Dest.Close();
	fs_Source.Close();
}
public static void GZipCompress3(string inFilename)
{
	string outFilename = inFilename + ".gz";

	// 目的檔
	using (FileStream fs_Dest = File.Create(outFilename))
	{
		// 壓縮資料流
		using (GZipStream gzipStream = new GZipStream(fs_Dest, CompressionMode.Compress))
		{
			// 來源檔
			using (FileStream fs_Source = File.OpenRead(inFilename))
			{
				// 來源檔 -> 壓縮資料流
				fs_Source.CopyTo(gzipStream);
				fs_Source.Flush();
				gzipStream.Flush();
			}
		}
	}
}

如何解壓縮資料流

解壓縮和壓縮的方法大至相同,只是對象不太一樣。
進行壓縮的時候, GZipStream 執行個體所包裹的資料流,是一個空的資料流,目的是要用來存放壓縮後的資料。
進行解壓縮的時候, GZipStream 執行個體所包裹的資料流,是一個包含原壓縮資料的資料流,通常就是指向壓縮檔的檔案資料流。

解壓縮資料的簡單步驟:

  • 建立一個資料流 Stream ,包裹要被解壓縮的資料。
  • 建立 GZipStream 物件。指定來源資料流,並將 CompressionMode 設定為 Decompress
  • 建立一個 byte[] 的緩衝區,用來暫存解壓縮後的資料。
  • 叫用 GZipStream.Read 方法,由 Stream 讀取資料,以執行解壓縮,並將解壓縮的資料存放在步驟3的緩衝區。
// 建立一個 DeflateStream
FileStream fs = new FileStream("testdata.def", FileMode.Open);  //開啟 Deflate 格式的壓縮檔
DeflateStream cs = new DeflateStream(fs, CompressionMode.Decompress);

MemoryStream memStream = new MemoryStream();
byte[] buffer = new byte[4096];
int offset = 0;
int count = 0;
while (true)
{
	count = cs.Read(buffer, 0, 4096);   //讀取解壓縮資料
	if (count > 0)
	{
		memStream.Write(buffer, 0, count);  //將 buffer 先丟到 memory stream
		offset += count;
	}
	else
		break;
}
byte[] data = memStream.ToArray();

cs.Close();
fs.Close();

string sContent = Encoding.Default.GetString(data);
public static void GZipDecompress2(string inFilename)
{
	FileInfo fi = new FileInfo(inFilename);

	//取得來源檔的資料流
	FileStream fs_Source = fi.OpenRead();

	// 建立 GZipStream 物件。指定來源資料流,並將 CompressionMode 設為 Decompress。
	GZipStream gzipStream = new GZipStream(fs_Source, CompressionMode.Decompress);

	//建立目的檔資料流
	string outFilename = inFilename.Remove(inFilename.Length - fi.Extension.Length);  //移除壓縮檔副檔名
	FileStream fs_Dest = File.Create(outFilename);

	// 將資料由 GZipStream CopyTo filestream 完成解壓縮
	gzipStream.CopyTo(fs_Dest);

	gzipStream.Close();
	fs_Source.Close();
	fs_Dest.Close();
}
public static void GZipDecompress3(string inFilename)
{
	FileInfo fi = new FileInfo(inFilename);
	string outFilename = inFilename.Remove(inFilename.Length - fi.Extension.Length);  //移除壓縮檔副檔名

	// 來源檔
	using (FileStream fs_Source = File.OpenRead(inFilename))
	{
		// 解壓縮資料流
		using (GZipStream gzipStream = new GZipStream(fs_Source, CompressionMode.Decompress))
		{
			// 目的檔
			using (FileStream fs_Dest = File.Create(outFilename))
			{
				// 解壓縮資料流 -> 目的檔
				gzipStream.CopyTo(fs_Dest);
				fs_Source.Flush();
				gzipStream.Flush();
			}
		}
	}
}

範例:利用 MemoryStream 壓縮與解壓縮資料

//=================================================================================
// 產生測試資料
//=================================================================================
StringBuilder tmp = new StringBuilder();
for (int i = 1; i <= 20000; i++)
	tmp.AppendLine(i.ToString() + " Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World");

byte[] source_data = Encoding.Default.GetBytes(tmp.ToString());


//=================================================================================
// 壓縮 byte[] 資料至 MemoryStream 中
//=================================================================================

// 建立一個 ZipStream
MemoryStream dest_stream = new MemoryStream();
GZipStream zipStream1 = new GZipStream(dest_stream, CompressionMode.Compress);

// 輸出壓縮資料
zipStream1.Write(source_data, 0, source_data.Length);
zipStream1.Close();

// 將 MemoryStream 裡的資料轉到 byte[]
byte[] compress_data = dest_stream.ToArray();

//=================================================================================
// 將資料載入到 MemoryStream 中,再解壓縮
//=================================================================================

// 將資料載入到 MemoryStream 中
MemoryStream source_stream = new MemoryStream(compress_data);

// 建立一個 ZipStream 
GZipStream zipStream2 = new GZipStream(source_stream, CompressionMode.Decompress);

// 輸出解壓縮後資料
MemoryStream tmpMemoryStream = new MemoryStream();
int theByte = zipStream1.ReadByte();
while (theByte != -1)
{
	tmpMemoryStream.WriteByte((byte)theByte);
	theByte = zipStream2.ReadByte();
}

// 將 tmpMemoryStream 裡的資料轉到 byte[]
byte[] decompress_data = tmpMemoryStream.ToArray();
zipStream2.Close();

string sContent = Encoding.Default.GetString(decompress_data);

沒有留言:

張貼留言