内存映射文件[1][2]
2015-03-31
原理[1]
内存映射文件包含虚拟内存中文件的内容。 利用文件与内存空间之间的映射,应用程序(包括多个进程)可以通过直接在内存中进行读写来修改文件。 从 .NET Framework 4开始,可以使用托管代码按照本机Windows函数访问内存映射文件的方式来访问内存映射文件
内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。[2]
有两种类型的内存映射文件
- 持久内存映射文件:持久文件是与磁盘上的源文件关联的内存映射文件。 在最后一个进程使用完此文件后,数据将保存到磁盘上的源文件中。 这些内存映射文件适合用来处理非常大的源文件。
- 非持久内存映射文件:非持久文件是未与磁盘上的源文件关联的内存映射文件。 当最后一个进程使用完此文件后,数据将丢失,并且垃圾回收功能将回收此文件。 这些文件适用于为进程间通信 (IPC) 创建共享内存。
进程、视图和管理内存
- 内存映射文件可以在多个进程之间进行共享。 进程可以通过使用由创建同一内存映射文件的进程所指派的公用名来映射到此文件。
- 若要使用一个内存映射文件,则必须创建该内存映射文件的完整视图或部分视图。 还可以创建内存映射文件的同一部分的多个视图,进而创建并发内存。 为了使两个视图能够并发,必须基于同一内存映射文件创建这两个视图。
- 如果文件大于应用程序用于内存映射的逻辑内存空间(在 32 位计算机上为 2 GB),则还需要使用多个视图。
- 有两种类型的视图:流访问视图和随机访问视图。 使用流访问视图可对文件进行顺序访问;对于非持久文件和 IPC,这是建议的方法。 在使用持久文件时,随机访问视图是首选方法。
- 由于内存映射文件是通过操作系统的内存管理器访问的,因此会自动将此文件分隔为多个页,并根据需要对其进行访问。 您不需要自行处理内存管理。
下图演示多个进程如何同时具有对同一内存映射文件的多个重叠视图:
Figure 1 内存映射文件的多个重叠视图
内存映射文件对象及其成员
表1:有关使用内存映射文件对象及其成员
方法 | 说明 |
MemoryMappedFile.CreateFromFile | 磁盘上的文件中获取表示持久内存映射文件的 MemoryMappedFile 对象。 |
MemoryMappedFile.CreateNew 或MemoryMappedFile.CreateOrOpen | 获取表示非持久内存映射文件(与磁盘上的文件不关联)的 MemoryMappedFile 对象。 |
MemoryMappedFile.OpenExisting | 获取现有内存映射文件(持久文件或非持久文件)的 MemoryMappedFile 对象。 |
MemoryMappedFile.CreateViewStream | 获取针对内存映射文件的顺序访问视图的 UnmanagedMemoryStream 对象。 |
MemoryMappedFile.CreateViewAccessor | 获取针对内存映射文件的随机访问视图的 UnmanagedMemoryAccessor 对象。 |
示例[2]
示例1:在同一进程内同时读写同一内存映射文件
Figure 2 在同一进程内同时读写同一内存映射文件
示例1代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 using System.IO; 11 using System.IO.MemoryMappedFiles; 12 13 namespace MemoryMappingFile 14 { 15 public partial class Form1 : Form 16 { 17 public Form1() 18 { 19 InitializeComponent(); 20 CreateMemoryMapFile(); 21 } 22 private const int FILE_SIZE = 512; 23 ///24 /// 引用内存映射文件 25 /// 26 private MemoryMappedFile memoryFile = null; 27 ///28 /// 用于访问内存映射文件的存取对象 29 /// 30 private MemoryMappedViewAccessor accessor1, accessor2,accessor; 31 ///32 /// 创建内存映射文件 33 /// 34 private void CreateMemoryMapFile() 35 { 36 try 37 { 38 memoryFile = MemoryMappedFile.CreateFromFile("MyFile.dat", FileMode.OpenOrCreate, "MyFile", FILE_SIZE); 39 //访问文件前半段 40 accessor1 = memoryFile.CreateViewAccessor(0, FILE_SIZE / 2); 41 //访问文件后半段 42 accessor2 = memoryFile.CreateViewAccessor(FILE_SIZE / 2, FILE_SIZE / 2); 43 //访问全部文件 44 accessor = memoryFile.CreateViewAccessor(); 45 //InitFileContent(); 46 lblInfo.Text = "内存文件创建成功"; 47 ShowFileContents(); 48 } 49 catch (Exception ex) 50 { 51 lblInfo.Text = ex.Message; 52 } 53 } 54 ///55 /// 关闭并释放资源 56 /// 57 private void DisposeMemoryMapFile() 58 { 59 if (accessor1 != null) 60 accessor1.Dispose(); 61 if (accessor2 != null) 62 accessor2.Dispose(); 63 if (memoryFile != null) 64 memoryFile.Dispose(); 65 } 66 67 private void frmMain_FormClosing(object sender, FormClosingEventArgs e) 68 { 69 DisposeMemoryMapFile(); 70 } 71 72 private void btnWrite1_Click(object sender, EventArgs e) 73 { 74 if (textBox1.Text.Length == 0) 75 { 76 lblInfo.Text = "请输入一个字符"; 77 return; 78 } 79 char[] chs = textBox1.Text.ToCharArray(); 80 char ch = chs[0]; 81 82 for (int i = 0; i < FILE_SIZE / 2; i += 2) 83 accessor1.Write(i, ch); 84 85 lblInfo.Text = "字符“" + ch + "”已写到文件前半部份"; 86 ShowFileContents(); 87 } 88 89 private void btnShow_Click(object sender, EventArgs e) 90 { 91 ShowFileContents(); 92 } 93 94 ///95 /// 初始化文件内容为可视的字符“0” 96 /// 97 private void InitFileContent() 98 { 99 for (int i = 0; i < FILE_SIZE; i += 2) 100 accessor.Write(i,'0'); 101 } 102 ///103 /// 显示文件内容 104 /// 105 private void ShowFileContents() 106 { 107 StringBuilder sb = new StringBuilder(FILE_SIZE); 108 sb.Append("上半段内容:\n"); 109 110 int j = 0; 111 for (int i = 0; i < FILE_SIZE / 2; i += 2) 112 { 113 sb.Append("\t"); 114 char ch = accessor.ReadChar(i); 115 sb.Append(j); 116 sb.Append(":"); 117 sb.Append(ch); 118 j++; 119 } 120 sb.Append("\n下半段内容:\n"); 121 122 for (int i = FILE_SIZE / 2; i < FILE_SIZE; i += 2) 123 { 124 sb.Append("\t"); 125 char ch = accessor.ReadChar(i); 126 sb.Append(j); 127 sb.Append(":"); 128 sb.Append(ch); 129 j++; 130 } 131 richTextBox1.Text = sb.ToString(); 132 } 133 134 private void btnWrite2_Click(object sender, EventArgs e) 135 { 136 if (textBox2.Text.Length == 0) 137 { 138 lblInfo.Text = "请输入一个字符"; 139 return; 140 } 141 char[] chs = textBox2.Text.ToCharArray(); 142 char ch = chs[0]; 143 144 for (int i = 0; i < FILE_SIZE / 2; i += 2) 145 accessor2.Write(i, ch); 146 lblInfo.Text = "字符“" + ch + "”已写到文件后半部份"; 147 ShowFileContents(); 148 } 149 }150 }
示例2:使用内存映射文件在进程间传送值类型数据
Figure2 使用内存映射文件在进程间传送值类型数据
示例2代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 using System.IO.MemoryMappedFiles; 11 using System.IO; 12 13 14 namespace MemoryMappingFile 15 { 16 ///17 /// 要共享的数据结构,注意,其成员不能是引用类型 18 /// 19 public struct MyStructure 20 { 21 public int IntValue 22 { 23 get; 24 set; 25 } 26 public float FloatValue 27 { 28 get; 29 set; 30 } 31 } 32 33 34 public partial class Form2 : Form 35 { 36 public Form2() 37 { 38 InitializeComponent(); 39 InitMemoryMappedFile(); 40 } 41 42 ///43 /// 内存映射文件的容量 44 /// 45 private const int FileSize = 1024 * 1024; 46 private MemoryMappedFile file = null; 47 private MemoryMappedViewAccessor accessor = null; 48 49 ///50 /// 初始化内存映射文件 51 /// 52 private void InitMemoryMappedFile() 53 { 54 file = MemoryMappedFile.CreateOrOpen("UseMMFBetweenProcess", FileSize); 55 accessor = file.CreateViewAccessor(); 56 lblInfo.Text = "内存文件创建或连接成功"; 57 } 58 59 ///60 /// 要共享的数据对象 61 /// 62 private MyStructure data; 63 64 ///65 /// 显示数据到窗体上 66 /// 67 private void ShowData() 68 { 69 textBox1.Text = data.IntValue.ToString(); 70 textBox2.Text = data.FloatValue.ToString(); 71 } 72 73 ///74 /// 根据用户输入更新数据 75 /// 76 private void UpdateData() 77 { 78 data.IntValue = int.Parse(textBox1.Text); 79 data.FloatValue = float.Parse(textBox2.Text); 80 } 81 82 private void btnSave_Click(object sender, EventArgs e) 83 { 84 try 85 { 86 UpdateData(); 87 accessor.Write(0, ref data); 88 lblInfo.Text = "数据已经保存到内存文件中"; 89 } 90 catch (Exception ex) 91 { 92 lblInfo.Text = ex.Message; 93 } 94 } 95 96 private void btnLoad_Click(object sender, EventArgs e) 97 { 98 accessor.Read (0, out data); 99 ShowData(); 100 lblInfo.Text = "成功从内存文件中提取了数据"; 101 } 102 103 private void frmMain_FormClosing(object sender, FormClosingEventArgs e) 104 { 105 if (accessor != null) 106 accessor.Dispose(); 107 if (file != null) 108 file.Dispose(); 109 } 110 }111 }
示例3:利用序列化技术通过内存映射文件实现进程通讯
Figure 3 利用序列化技术通过内存映射文件实现进程通讯
示例3代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 using System.IO; 11 using System.IO.MemoryMappedFiles; 12 using System.Runtime.Serialization; 13 using System.Runtime.Serialization.Formatters.Binary; 14 15 16 namespace MemoryMappingFile 17 { 18 19 public partial class Form3 : Form 20 { 21 public Form3() 22 { 23 InitializeComponent(); 24 InitMemoryMappedFile(); 25 } 26 27 [Serializable] 28 class MyPic 29 { 30 public Image pic; 31 public string picInfo; 32 } 33 34 ///35 /// 图片 36 /// 37 private Image bmp 38 { 39 get 40 { 41 return pictureBox1.Image; 42 } 43 set 44 { 45 pictureBox1.Image = value; 46 } 47 } 48 49 ///50 /// 图片说明 51 /// 52 private string info 53 { 54 get 55 { 56 return txtImageInfo.Text; 57 } 58 set 59 { 60 txtImageInfo.Text = value; 61 } 62 } 63 64 private MemoryMappedFile memoryFile = null; 65 66 private MemoryMappedViewStream stream = null; 67 68 ///69 /// 最大容量:10M 70 /// 71 private const int FileSize = 1024 * 1024 * 10; 72 73 ///74 /// 创建内存映射文件,获取其读写流 75 /// 76 private void InitMemoryMappedFile() 77 { 78 try 79 { 80 memoryFile = MemoryMappedFile.CreateOrOpen("UseMMFBetweenProcess2", FileSize); 81 stream = memoryFile.CreateViewStream(); 82 } 83 catch (Exception ex) 84 { 85 MessageBox.Show(ex.Message); 86 Close(); 87 } 88 } 89 ///90 /// 释放相关资源 91 /// 92 private void DisposeMemoryMappedFile() 93 { 94 if (stream != null) 95 stream.Close(); 96 if (memoryFile != null) 97 memoryFile.Dispose(); 98 } 99 100 private void btnLoadPic_Click(object sender, EventArgs e)101 {102 ChooseImageFile();103 }104 105 //选择图片 106 private void ChooseImageFile()107 {108 if (openFileDialog1.ShowDialog() == DialogResult.OK)109 {110 bmp = new Bitmap(openFileDialog1.FileName);111 }112 }113 //根据用户设定的信息创建对象 114 private MyPic CreateMyPicObj()115 {116 MyPic obj = new MyPic();117 obj.pic = bmp;118 obj.picInfo = info;119 return obj;120 }121 122 ///123 /// 将MyPic对象保存到内存映射文件中 124 /// 125 private void SaveToMMF()126 {127 try128 {129 MyPic obj = CreateMyPicObj();130 IFormatter formatter = new BinaryFormatter();131 stream.Seek(0, SeekOrigin.Begin);132 formatter.Serialize(stream, obj);133 MessageBox.Show("对象已保存到内存映射文件中");134 }135 catch (Exception ex)136 {137 MessageBox.Show(ex.Message);138 }139 }140 141 private void LoadFromMMF()142 {143 try144 {145 // CreateMyPicObj(); 146 IFormatter formatter = new BinaryFormatter();147 stream.Seek(0, SeekOrigin.Begin);148 MyPic obj = formatter.Deserialize(stream) as MyPic;149 if (obj != null)150 {151 bmp = obj.pic;152 info = obj.picInfo;153 }154 }155 catch (Exception ex)156 {157 MessageBox.Show(ex.Message);158 }159 }160 161 private void frmMain_FormClosing(object sender, FormClosingEventArgs e)162 {163 DisposeMemoryMappedFile();164 }165 166 private void btnSaveToMMF_Click(object sender, EventArgs e)167 {168 SaveToMMF();169 }170 171 private void btnLoadFromMMF_Click(object sender, EventArgs e)172 {173 LoadFromMMF();174 }175 176 177 }178 }
参考
[1]
[2]