はじめに
数ギガバイトの大きさのファイルを操作して、そのデータを読み書きする必要があるとします。1つの選択肢は、シーケンシャルストリームを使ってファイルにアクセスする方法です。ファイルの先頭から末尾までアクセスする必要がある場合は、この方法が適しています。しかし、ランダムアクセスが必要な場合は困った問題が生じます。ストリームをシークする処理に時間がかかりすぎるという問題です。
Windows API開発の経験者なら、メモリマップドファイル(MMF)と呼ばれる昔ながらの手法をご存じでしょう。メモリマップドファイル(ファイルマッピング)とは、ファイルをメモリ内に読み込んで、アプリケーションのアドレス空間の連続するブロックとしてファイルを操作する機能のことです。この機能を使うと、ファイルの読み書きは適切なメモリ位置にアクセスするだけで済むようになります。実際、オペレーティングシステムのローダーがアプリケーションのEXEファイルやDLLファイルをフェッチしてコードを実行するときは、見えないところでファイルマッピングが使われています。
メモリマップドファイルを.NETアプリケーションから使うこと自体は目新しい処理ではありません。既に.NET 1.0に備わっていたプラットフォーム呼び出し(P/Invoke)を使って、基になるオペレーティングシステムのAPIを使用できたからです。ただし、.NET 4.0では、Windows APIを直接使わずに、マネージコードのすべての開発者がメモリマップドファイルを利用できるようになっています。
メモリマップドファイルというと、開発者は大きなファイルを連想しがちですが、メモリマッピングを使ってアクセスするファイルの大小に実用上の制限はありません。大きなファイルにメモリマッピングを使うとプログラミングは容易になりますが、小さなファイルを使ったときの方がパフォーマンスの向上が見られることがあります。小さなファイルはまるごとファイルシステムキャッシュに収まるからです。
本稿の内容とコードは、2009年5月から公開されている.NET 4.0 Beta 1リリースに基づいています。リリース前のソフトウェアにはよくあることですが、技術上の詳細、クラス名、および使用できるメソッドは、最終的なRTM版の.NETとは異なる可能性があります。ベータライブラリを扱った調査や開発を行うときは、常にこの点に留意してください。
新しい名前空間とクラス
.NET 4.0開発者がメモリマップドファイルを操作するためのクラスは、新しいSystem.IO.MemoryMappedFiles
名前空間にあります。今のところ、この名前空間には、ファイルマッピングにアクセスし、セキュリティで保護するための4つのクラスといくつかの列挙があります。実装自体は、アセンブリSystem.Core.dll内にあります。
開発者にとって最も重要なクラスは、MemoryMappedFile
クラスです。このクラスを使って、メモリマップドオブジェクトを作成し、そこからさらにビューアクセサオブジェクトを作成できます。このアクセサを使うと、ファイルからマップされたメモリブロックを直接操作できます。メモリブロックの操作には、一連の便利な読み取りメソッドと書き込みメソッドを使用できます。
マネージコードの世界では、直接ポインタは適切なプログラミング手法と見なされないので、筋を通すためにこのようなアクセスオブジェクトが必要になります。従来のネイティブコードでのWindows API開発であれば、単にメモリブロックの先頭のポインタを取得する方法を使うでしょう。
とは言っても、メモリマップドファイルと、必要なアクセサオブジェクトを取得する操作は、3つの簡単なステップを実行するだけで済みます。まず、ディスク上の(既存の)ファイルをポイントするファイルストリームオブジェクトを作成します。次に、このファイルからマッピングオブジェクトを作成し、最後にアクセサオブジェクトを作成します。次にC#のコード例を示します。
FileStream file = new FileStream( @"C:\Temp\MyFile.dat", FileMode.Open); MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file); MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor();
このコードでは、まずSystem.IO.FileStream
クラスを使ってファイルをオープンしています。次に、MemoryMappedFile
クラスのCreateFromFile
静的メソッドにストリームオブジェクトのインスタンスを渡しています。最後に、MemoryMappedFile
クラスのCreateViewAccessor
メソッドを呼び出しています。
上のコードでは、パラメータを指定せずにCreateViewAccessor
メソッドを呼び出しています。この場合、マッピングはファイルの先頭(オフセットゼロ)から始まり、ファイルの最後のバイトで終わります。しかし、ファイルの任意の部分をマップすることも簡単にできます。例えば、ファイルのサイズが1ギガバイトある場合に、1メガバイトの位置を基準とするビューを10,000バイトのビューサイズでマップできます。これを行うには、次のような呼び出しを使います。
MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024 * 1024, 10000);
後から、このようなマップされたビューの高度な使い方を紹介します。しかしその前に、ビューからの読み取りについて説明する必要があります。