.NET Frameworkでは、System.IO.Compression.GZipStreamやSystem.IO.Compression.DeflateStreamクラスを用いて、データを圧縮・展開することができます。この2つのクラスは全く同じように使えるため、以下ではGZipStreamで話を進めます。
以下のコードは、指定したbyte配列をGZipStreamで圧縮してMemoryStreamに出力するサンプルです。MemoryStreamのところをFileStreamにすればメモリではなくファイルに書き出すこともできます。
using System.IO; using System.IO.Compression; using System.Text; class Program { static void Compress(MemoryStream ms, byte[] data) { using (GZipStream gzs = new GZipStream(ms, CompressionMode.Compress)) { gzs.Write(data, 0, data.Length); } } static void Main(string[] args) { MemoryStream ms = new MemoryStream(); byte[] data = Encoding.UTF8.GetBytes("hogehogefugafugapiyopiyo"); Compress(ms, data); } }
このように、ReadやWriteメソッドを使ってデータを読み書きするのですが、これらのメソッドは引数にbyte[]をとります。ですから、アンマネージなバイト列の先頭ポインタ(IntPtr)を持っていた場合はそのままでは圧縮できません。これを何とかしてがんばる方法をいくつか考えてみました。
なお、展開の方も同様の考え方で行えるため、ここでは割愛します。
1. IntPtrからbyte[]に変換する
すぐ思いつくのはこの方法です。バイト列と同じ大きさのマネージbyte配列を別に用意して、Marshal.Copyを使ってコピーし、GZipStreamに丸投げするだけです。
static void Compress(MemoryStream ms, IntPtr data, int size) { byte[] buffer = new byte[size]; Marshal.Copy(data, buffer, 0, size); using (GZipStream gzs = new GZipStream(ms, CompressionMode.Compress)) { gzs.Write(buffer, 0, size); } }
しかしバイト長sizeが大きい場合は、bufferの確保とコピーのコストが高くつきます。
2. 1バイトずつコピーする
最もメモリは節約できます。
static void Compress(MemoryStream ms, IntPtr data, int size) { using (GZipStream gzs = new GZipStream(ms, CompressionMode.Compress)) { for(int offset = 0; offset < size; offset++) { gzs.WriteByte(Marshal.ReadByte(ptr, offset)); } } }
しかし処理速度は期待できません。
3. 少しずつコピーする
1と2の折衷案。さすがに1バイトごとはやりすぎだけど、一気にコピーするほどメモリは無い。そこで例えば128KBずつコピーする、ということにします。
static void Compress(MemoryStream ms, IntPtr data, int size) { // バッファ長 const int Length = 1024 * 128; byte[] buffer = new byte[Length]; using (GZipStream gzs = new GZipStream(ms, CompressionMode.Compress)) { for (int offset = 0; offset < size; offset += Length) { // バイト列から読み込むバイト長。 // 残りの長さがLengthを切ったらその残りの長さにする。 int length = (offset + Length < size) ? Length : size - offset; // 読み込むバイト列の先頭ポインタをずらす IntPtr ptr = new IntPtr(data.ToInt64() + offset); Marshal.Copy(ptr, buffer, 0, length); gzs.Write(buffer, 0, length); } } }