一、什么是内存映射文件
内存映射文件,是由一个文件到一块内存的映射。 共享内存(SharedMemory)实际就是文件映射的一种特殊情况。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。 使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。
二、什么情况下要用内存映射
对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的。
映射文件的另一个重要应用就是用来支持永久命名的共享内存。要在两个应用程序之间共享内存,可以在一个应用程序中创建一个文件并映射之,然后另一个应用程序可以通过打开和映射此文件把它作为共享的内存来使用。
内存映射文件是由一个文件到进程地址空间的映射。Win32中,每个进程有自己的地址空间,一个进程不能轻易地访问另一个进程地址空间中的数据。Win32系统允许多个进程(运行在同一计算机上)使用内存映射文件来共享数据。实际上,其他共享和传送数据的技术,诸如使用SendMessage或者PostMessage,都在内部使用了内存映射文件。
总结,1.大文件处理, 2.进程间通信
缺点: 多个进程使用时需要添加同步机制,比如信号量机制。
三。代码示例
1.临时性文件映射
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
//创建一个共享内存,并写入数据
namespace ConsoleAppTwoIPC
{
class Program
{
static void Main(string[] args)
{
//定义内存大小
int size = 1024;
//创建共享内存
//临时共享内存,最后一个进程使用完成后,就会销毁
MemoryMappedFile shareMemory =
MemoryMappedFile.CreateOrOpen("global_share_memory", size);
Console.WriteLine("创建共享内存完成...");
//线程等待10秒
System.Threading.Thread.Sleep(10000);
int i = 1;
while (true)
{
var stream = shareMemory.CreateViewStream(0, size);
string value = "Hello World" + i++.ToString();
byte[] data = System.Text.Encoding.UTF8.GetBytes(value);
stream.Write(data, 0, data.Length);
Console.WriteLine(value);
var val = stream.Capacity;
stream.Flush();
stream.Dispose();
System.Threading.Thread.Sleep(10000);
}
Console.ReadKey();
}
}
}
//另一个进程读取该共享内存的数据,c++实现
int main(int argc, char* argv[])
{
//LPVOID pBuffer;
std::wstring strMapName(L"global_share_memory");
HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, strMapName.c_str());
if (nullptr == hMap)
{
std::cout << "无共享内存..." << std::endl;
}
else
{
while (true)
{
Sleep(1000);
auto pBuffer = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
std::cout << "读取共享内存数据:" << (char*)pBuffer << std::endl;
}
}
return 0;
}
2.永久性文件映射,把磁盘上的一个文件映射到内存上
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
namespace ConsoleAppTwoIPC
{
class Program
{
static void Main(string[] args)
{
string mapName = "global_share_memory";
// 创建一个1M的空文件,用于文件映射
//long length = 1024L *1024L * 1L; // 1M
// FileStream fs = new FileStream(@"e:\223.txt", FileMode.Create);
//fs.Seek(length, SeekOrigin.Begin);
//fs.WriteByte(0);
//fs.Close();
//创建一个该文件的内存映射,并写入数据
var firstMapFile = MemoryMappedFile.CreateFromFile(@"e:\223.txt", FileMode.Open, mapName);
var accessor = firstMapFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.ReadWrite);
string value = "A";
byte[] data = System.Text.Encoding.UTF8.GetBytes(value);
accessor.WriteArray(2, data, 0, data.Length);
Console.ReadKey();
// 打开已经存在的内存映射文件
// 第一个参数为这个内存映射文件的名称
// 【此处的代码可以放在另一个进程中】
var secondMapFile = MemoryMappedFile.OpenExisting(mapName);
using (var secondAccessor = secondMapFile.CreateViewAccessor(/*offset, length*/))
{
// 读取映射文件内容
var bytes = new byte[1024];
secondAccessor.ReadArray(0, bytes, 0, bytes.Length);
secondAccessor.Dispose();
string str = Encoding.UTF8.GetString(bytes);
Console.WriteLine(str);
}
释放内存映射文件资源
firstMapFile.Dispose();
secondMapFile.Dispose();
}
}
}
四、内存映射文件二三事
关于内存映射文件,我们还需要了解以下几点
- 默认情况下,在调用
MemoryMappedFile.CreateFromFile
方法时如果不指定文件容量,那么,创建的内存映射文件的容量等同于文件的大小 - 如果磁盘上的文件是新创建的,那么必须为它指定容量(
MemoryMappedFile.CreateFromFile
的capacity
参数) - 在指定内存映射文件的容量时,其值不能小于磁盘文件的现有长度。如指定了一个大于磁盘文件大小的容量,则磁盘文件的大小会被扩充至指定容量
- 当不再使用一个
MemoryMappedFile
对象时,我们应该及时地调用Dispose
方法释放它占有的资源(进程结束后,其资源也会被释放,但我们应该养成良好的习惯,主动释放)