內存映射文件[1][2]
2015-03-31
原理
有兩種類型的內存映射文件
進程、視圖和管理內存
內存映射文件對象及其成員
示例
示例1:在同一進程內同時讀寫同一內存映射文件
示例2:使用內存映射文件在進程間傳送值類型數據
示例3:利用序列化技術通過內存映射文件實現進程通訊
參考
原理[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 /// <summary> 24 /// 引用內存映射文件 25 /// </summary> 26 private MemoryMappedFile memoryFile = null; 27 /// <summary> 28 /// 用於訪問內存映射文件的存取對象 29 /// </summary> 30 private MemoryMappedViewAccessor accessor1, accessor2,accessor; 31 /// <summary> 32 /// 創建內存映射文件 33 /// </summary> 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 /// <summary> 55 /// 關閉並釋放資源 56 /// </summary> 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 /// <summary> 95 /// 初始化文件內容為可視的字符“0” 96 /// </summary> 97 private void InitFileContent() 98 { 99 for (int i = 0; i < FILE_SIZE; i += 2) 100 accessor.Write(i,'0'); 101 } 102 /// <summary> 103 /// 顯示文件內容 104 /// </summary> 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 /// <summary> 17 /// 要共享的數據結構,注意,其成員不能是引用類型 18 /// </summary> 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 /// <summary> 43 /// 內存映射文件的容量 44 /// </summary> 45 private const int FileSize = 1024 * 1024; 46 private MemoryMappedFile file = null; 47 private MemoryMappedViewAccessor accessor = null; 48 49 /// <summary> 50 /// 初始化內存映射文件 51 /// </summary> 52 private void InitMemoryMappedFile() 53 { 54 file = MemoryMappedFile.CreateOrOpen("UseMMFBetweenProcess", FileSize); 55 accessor = file.CreateViewAccessor(); 56 lblInfo.Text = "內存文件創建或連接成功"; 57 } 58 59 /// <summary> 60 /// 要共享的數據對象 61 /// </summary> 62 private MyStructure data; 63 64 /// <summary> 65 /// 顯示數據到窗體上 66 /// </summary> 67 private void ShowData() 68 { 69 textBox1.Text = data.IntValue.ToString(); 70 textBox2.Text = data.FloatValue.ToString(); 71 } 72 73 /// <summary> 74 /// 根據用戶輸入更新數據 75 /// </summary> 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<MyStructure>(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<MyStructure>(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 /// <summary> 35 /// 圖片 36 /// </summary> 37 private Image bmp 38 { 39 get 40 { 41 return pictureBox1.Image; 42 } 43 set 44 { 45 pictureBox1.Image = value; 46 } 47 } 48 49 /// <summary> 50 /// 圖片說明 51 /// </summary> 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 /// <summary> 69 /// 最大容量:10M 70 /// </summary> 71 private const int FileSize = 1024 * 1024 * 10; 72 73 /// <summary> 74 /// 創建內存映射文件,獲取其讀寫流 75 /// </summary> 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 /// <summary> 90 /// 釋放相關資源 91 /// </summary> 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 /// <summary> 123 /// 將MyPic對象保存到內存映射文件中 124 /// </summary> 125 private void SaveToMMF() 126 { 127 try 128 { 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 try 144 { 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] C#內存映射文件學習
[2] 內存映射文件