在前面,你已经看到如何使用内存映射文件来轻松访问一个文件的内容,通过一些简单的内存操作。接下来的步骤就是学习如何使用这个知识在你的应用程序中和程序间分享内存。
当映射一个文件内容到内存中的时候,除了其他事情外,你需要指定在哪个磁盘上放文件和你想要映射文件的哪个部分。这个很简单,但是也有不是显而易见的,就是你要多次映射相同的文件,即使映射区是相同的或是重叠的(图2)。
图2. 文件的各个部分可以映射多次。
利用这个知识,可以让多线程访问文件内容,无需担心并发和锁定。这只不过是知道如何从内存块中读取和写入。而且有了view accessor类了,你已经知道如何去做了。这里有一个例子是怎样从文件开始映射到不止一个的accessor对象。当然,你的文件名每次必须与你创建的新的view accessor想匹配。如果你使用相同内存映射两次,这个是很容易做到的:
...
MemoryMappedViewAccessor accessor1 =
mmf.CreateViewAccessor();
MemoryMappedViewAccessor accessor2 =
mmf.CreateViewAccessor();
// write
byte writeChr = Encoding.ASCII.GetBytes("Z")[0];
accessor1.Write(0, writeChr);
// read
byte readChr = accessor2.ReadByte(0);
string status = (readChr == writeChr) ? "Match!" : "No match!";
MessageBox.Show(status); // match
要注意的是一旦写入视图的内存块完成,文件内容就会改变。操作系统可能不会立即将已改变的数据刷新到磁盘,但是通常这是“近即时”(near-instant)。没有单独的错误或是刷新操作的需要;这是内存映射文件美丽之处的其中之一。
为了在程序间分享映射文件,必须给你的视图起个名字。这个名字允许你在一个以上的程序中打开一个同步视图,而且不用说这个名字在系统中的对象名字间必须是唯一的。假设你想要从一个程序发送一个字符串到另一个程序。这里是打开一个已命名的内存映射视图的代码,而且写入一个简单的字符串到视图中:
MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(
"my-mmf-map-name", 1000);
MemoryMappedViewAccessor accessor =
mmf.CreateViewAccessor();
string message = "Hello, Memory-Mapped World!";
byte[] asciiBytes = Encoding.ASCII.GetBytes(message);
accessor.WriteArray(0, asciiBytes, 0, asciiBytes.Length);
MessageBox.Show("Message written.");
请注意在上述的代码中没有包含数据的物理文件。由于这个原因,你需要在调用CreateOrOpen方法时指定一个容量参数。在上面的代码中,这个设置为1,000字节。这个容量定义了内存块的大小。回到程序间分享信息的例子 ,下一步是在其他程序中使用相同命名的试图来读取字符串栈:
MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(
"my-mmf-map-name", 1000);
MemoryMappedViewAccessor accessor =
mmf.CreateViewAccessor();
byte byteValue;
int index = 0;
StringBuilder message = new StringBuilder();
do
{
byteValue = accessor.ReadByte(index);
if (byteValue != 0)
{
char asciiChar = (char)byteValue;
message.Append(asciiChar);
}
index++;
} while (byteValue != 0);
MessageBox.Show("Found text: \""+message+"\".");
在以上的代码中,第二个程序通过使用MemoryMappedFile类的CreateOrOpen static方法打开相同的内存映射视图。然后,accessor对象像以前一样被创建,而且数据一个字节一个字节的被读取,直到找到零终结器。然后,信息被处理,这种情况会在屏幕上显示出来。这是在程序间完成inter-process communication (IPC)的最简单的方法!
创建,扩展和截取文件
到目前为止,你已经学习了如何访问在磁盘上已经存在的内存映射文件,或者即时创建inter-process communications。如果你想从无到有的创建一个文件,扩展或截取文件映射到内存该怎样做呢?幸运的是,这三种情况会直截了当的完成。
第一,如果你想要创建一个新文件在其上创建一个内存映射视图,你需要执行以下的代码:
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字节长度的这个情况,你将怎样忽略局限继续写入?如果你映射一个试图并且使用accessor对象Write方法来试着写入过去的文件容量(大小),这个操作会失败(可能未来的.NET4.0版本会有不同的表现)。这样,你不能通过写入过去的文件结尾而简单的扩展文件,你可以用一些流。
然后你怎样扩展一个文件呢?答案就在于CreateFromFile方法的容量参数在MemoryMappedFile类中调用。如果你指定一个比在磁盘上的文件更大的容量,然后Windows将扩展这个文件以匹配所给出的容量。显然,如果磁盘有足够的空间这个做法会成功,这样即使你有足够的内存,容量增加不会(总是)一直在工作。
以下代码显示如何扩展先前描述的1,000字节的文件到2,000字节:
FileStream file = new FileStream(
@"C:\Temp\MyNewFile.dat", FileMode.Open);
MemoryMappedFile mmf =
MemoryMappedFile.CreateFromFile(file, null, 2000);
容量参数被定义为一个C#,意思是一个64-bit值(System.Int64)。你不会一次限制在2 gigabyte字节,但是可以代替使用更大的视图。实际上来说,唯一的限制是你的应用程序中的空闲虚拟地址空间,大约8 terabytes,如果你有一个64-bit Windows操作系统并且编译你的.NET应用程序成为一个64-bit的应用程序(在Visual Studio中的x64平台目标模式)。在常规的32-bit系统上,限制通常低于2GB,取决于系统安装和内存的使用。第三个常见的操作,截取,和前两个情况有一点不同:截取文件必须在文件层完成。如果你试着指定一个容量参数值小于磁盘上的实际文件,你将会得到一个错误的提示就是容量值不能小于文件大小。因此。你必须选择其他的方法,其中一个方法就是使用FileStream的SetLength方法。
为了获得文件的大小,你需要测试这个流的长度属性,或使用System.IO.FileInfo类的相同命名的属性。
总结
在本文中,你学习了关于内存映射文件和在.NET Framework 4.0中支持它们的类。内存映射是一项有用的技术,通过一些简单的内存操作允许用简单的方法来读取和写入文件。不需要任何stream seeking,而且你不必担心文件过大而无法适应内存:只是映射你所需要的部分文件,你可以完成。
内存映射不但在一个应用程序的线程间分享数据是很有用的,而且在相同的系统上运行的程序间分享数据也是有益的。要在程序间分享数据,你需要给你映射的视图对象起一个独特的名字。如果这个名字与其他程序中的想匹配,这个数据就会自动的被分享。
用.NET4.0,你可以使用管理类来使用内存映射文件。读取和写入到同一个映射视图是通过一个accesssor对象完成的,也可以通过传统流的方式完成。获取accessor对象本身通常有三个过程:首先使用FileStream打开文件,然后创建一个内存映射对象,最后从映射对象中获得accessor。
Access对象允许你轻松的读取和写入最原始的数据类型,但是通用的数据类型支持让你获得更复杂的类型,包括数组。字符串可以一个字节一个字节的读取和写入;你需要记住的是做一些适当的编译来进行正确的读取和写入。
内存映射是访问文件数据的一种宝贵的技术,无论文件的大小。用.NET 4.0,管理代码开发者们应该学习这个新方法,而且无论何时需要它的时候就使用它。它成为更多访问文件的传统方法的最好的替代品。