ファイルの作成、拡張、および切り詰め
ここまでは、ディスク上に既に存在するか、プロセス間通信用にその場で作成したメモリマップドファイルにアクセスする方法を説明してきました。それでは、ファイルを新たに作成したい場合や、メモリにマップされたファイルを拡張したり切り詰めたりしたい場合はどうすればよいでしょうか。幸い、この3つの操作は簡単に実装できます。
まず、メモリにマップされるビューの基になる新しいファイルを作成したい場合は、次のようなコードを実行します。
FileStream file = new FileStream(
@"C:\Temp\MyNewFile.dat", FileMode.CreateNew);
MemoryMappedFile mmf =
MemoryMappedFile.CreateFromFile(file, null, 1000);
MemoryMappedViewAccessor accessor =
mmf.CreateViewAccessor();
ここでは、FileStreamのコンストラクタへの呼び出しでCreateNewモードを指定して、新しいファイルを作成しています。これによって、長さゼロの新しいファイルがディスク上に作成されます。このような空のファイルをビューの作成に直接使うことはできないので、CreateFromFileメソッド呼び出しで容量パラメータを指定する必要があります。上の例では、ファイルの容量は1,000バイトになります。他にファイルへの書き込みが何もなければ、ファイルにはゼロの値、つまりNull文字が入ります。
このような1,000バイトの長さのファイルがある場合に、その長さの制限を超えて書き込みを続けるにはどうすればよいでしょうか。ビューをマップし、アクセサオブジェクトのWriteメソッドを使って、ファイルの容量(サイズ)を超えて書き込もうとすると、操作は警告なしで失敗します(今後の.NET 4.0のリリースでは動作が変わる可能性があります)。従って、ストリームを使ったインスタンスとは異なり、単にファイルの末尾を超えて書き込むことによってファイルを拡張することはできません。
ファイルを拡張するにはどうすればよいでしょうか。その答えは、やはりMemoryMappedFileクラスのCreateFromFileメソッド呼び出しの容量パラメータにあります。ディスク上の実ファイルより大きい容量を指定すると、Windowsは指定した容量に合わせてファイルを拡張します。当然、これには十分な空きディスク領域が必要なので、たとえメモリが十分にあっても容量の拡張が常にうまくいくとは限りません。
次のコードは、先ほど作成した1,000バイトのファイルを2,000バイトに拡張する方法を示しています。
FileStream file = new FileStream(
@"C:\Temp\MyNewFile.dat", FileMode.Open);
MemoryMappedFile mmf =
MemoryMappedFile.CreateFromFile(file, null, 2000);
容量パラメータは、C#のlong型として定義されます。つまり符号付きの64ビット値(System.Int64)になります。このため、一度に2ギガバイトのビューに制限されることなく、はるかに大きいビューを使うことができます。事実上、アプリケーションの空き仮想アドレス空間が唯一の制限となります。これは64ビット版のWindowsで、.NETアプリケーションを64ビットアプリケーションとしてコンパイルした場合(Visual Studioのx64プラットフォームターゲットモード)、8テラバイト前後になります。通常の32ビットシステムでは、システムの構成と使用可能なメモリによって、一般に制限は2GB未満になります。もう1つの操作である切り詰めは、前の2つの操作とは少し異なる方法で行います。ファイルの切り詰めは、ファイルレベルで行う必要があります。ディスク上の実ファイルより小さい容量パラメータ値を指定しようとしても、ファイルサイズより小さい容量の値は指定できないというエラーが表示されます。このため、別のやり方を選ぶ必要があります。その1つは、FileStreamのSetLengthメソッドを使う方法です。
ファイルのサイズを取得するには、ストリームのLengthプロパティを調べるか、System.IO.FileInfoクラスの類似の名前のプロパティを使います。
まとめ
本稿では、.NET Framework 4.0におけるメモリマップドファイルと、それをサポートするマネージクラスについて説明しました。メモリマッピングは、単純なメモリ操作を使ってファイルへの読み書きを簡単に行うことができる便利な手法です。ストリームのシークは不要で、ファイルがメモリに収まりきらないほど大きくても気にする必要はありません。単にファイルの一部を必要に応じてマップすれば済むからです。
メモリマッピングは、アプリケーションのスレッド間に加えて、同じシステムで実行中のプロセス間でデータを共有する場合にも利用できます。プロセス間でデータを共有する場合に必要なのは、マップされたビューオブジェクトに一意の名前を付けることだけです。この名前が他のプロセスでも一致していれば、データは自動的に共有されます。
.NET 4.0では、マネージクラスを使ってメモリマップドファイルを操作できます。マップされたビューの読み書きはアクセサオブジェクトを使って行います。また、昔ながらのストリームの形でビューにアクセスすることもできます。アクセサオブジェクト自体を取得する場合、通常は3つの手順を実行します。まず、FileStreamを使ってファイルをオープンします。次に、メモリマッピングオブジェクトを作成し、最後にマッピングオブジェクトを使ってアクセサを取得します。
アクセサオブジェクトを使うと、最も基本的なデータ型を簡単に読み書きできますが、ジェネリックデータ型がサポートされているので、配列などのより複雑な型も取得できます。文字列はバイト単位で読み書きできます。正しく読み書きするための適切なエンコーディングを忘れずに行ってください。
メモリマッピングは、大小を問わず、ファイルのデータにアクセスするための有用な手法です。マネージコードの開発者は、.NET 4.0で提供されるこの新しい手法を身に付けて、いつでも使えるようにしておく必要があります。従来の手法に代わる優れたファイルアクセスの手段として、メモリマッピングが役立つはずです。
