.NET 4.0 New Feature - Memory Mapped File

.NET Framework 在 4.0 新增了記憶體對應檔案(Memory Mapped File)功能,將以前需透過 API 才能使用的功能包在 .NET Framework 的 System.IO.MemoryMappedFiles 命名空間中,可用以編輯大小極大的檔案、減少IO的存取次數、提高檔案處理的效能、與在多個處理序中共享其內容。


要使用記憶體對應檔案物件,首先要先建立記憶體對應檔案物件,須了解到記憶體對應檔案有兩種類型;

  1. 保存的記憶體對應檔案
  2. 非保存的記憶體對應檔案


保存的記憶體對應檔案顧名思義其記憶體對應檔案的內容會與磁碟中的檔案做對應,可將檔案內容放至記憶體對應檔案中用以處理極大的檔案,當處理完畢資料會自動回存回對應檔案。


非保存的記憶體對應檔案則沒有與磁碟中的檔案對應,其所存放的值只是記憶體中的資料,可用來作處理序間通訊(IPC)的共用記憶體,當處理完畢其記憶體對應檔案的資料會被捨棄,且會被GC回收。


使用上是透過 MemoryMappedFile 類別內含的 CreateNew、 CreateOrOpen、與 CreateFromFile 等方法來建立記憶體對應檔案物件。

Method Description
CreateNew 建立非保存的記憶體對應檔案物件
CreateOrOpen 建立或開啟非保存的記憶體對應檔案物件
CreateFromFile 建立保存的記憶體對應檔案物件


若是有已建好的記憶體對應檔案,也可以透過 MemoryMappedFile 類別內含的 OpenExisting 方法將已經建立好的記憶體對應檔案物件開啟。


有了記憶體對應檔案物件後,剩下的就是透過記憶體對應檔案檢視去對其作存取的動作。一個記憶體對應檔案物件可產生多個記憶體對應檔案檢視,可設定要針對記憶體對應檔案物件整體或是部份來檢視,需特別注意到記憶體對應檔案檢視也分為兩種類型:

  1. 資料流存取檢視
  2. 隨機存取檢視


資料流存取檢視透過 MemoryMappedFile.CreateViewStream 建立,採循序存取的方式處理資料,適用於非保存的記憶體對應檔案。


隨機存取檢視透過 MemoryMappedFile.CreateViewAccessor 建立,採隨機存取的方式處理資料,適用於保存的記憶體對應檔案。


這邊來看個非保存的記憶體對應檔案物件的使用範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Imports System.IO.MemoryMappedFiles
Imports System.Collections.Specialized
Imports System.Runtime.Serialization.Formatters.Binary

Module Module1

Sub Main()
Const capacity As Integer = 512
Const mmfKey As String = "Larry"
Dim levelUpBlog As New Blog With {.Title = "Level Up", .Owner = "Larry"}

'顯示寫入前資訊
Console.WriteLine("資料寫入記憶體對應檔案前...")
ShowBlogInfo(levelUpBlog)

'Write to memory
Using mmf As MemoryMappedFile = MemoryMappedFile.CreateNew(mmfKey, capacity)
Using mmvs = mmf.CreateViewStream
Dim bs As New BinaryFormatter
bs.Serialize(mmvs, levelUpBlog)
End Using

'Read from memory
Using existedMMF As MemoryMappedFile = MemoryMappedFile.OpenExisting(mmfKey)
Using mmvs = existedMMF.CreateViewStream
Dim bs As New BinaryFormatter
Dim blogObj As Blog = bs.Deserialize(mmvs)

Console.WriteLine(New String("="c, 50))
Console.WriteLine("資料從記憶體對應檔案讀出並增加文章...")
ShowBlogInfo(blogObj)

blogObj.Articles.Add("文章一 初來點部落:...(略)...")
mmvs.Seek(0, IO.SeekOrigin.Begin)
bs.Serialize(mmvs, blogObj)
End Using
End Using

'Read from memory
Using existedMMF As MemoryMappedFile = MemoryMappedFile.OpenExisting(mmfKey)
Using mmvs = existedMMF.CreateViewStream
Dim bs As New BinaryFormatter
Dim blogObj As Blog = bs.Deserialize(mmvs)

Console.WriteLine(New String("="c, 50))
Console.WriteLine("資料從記憶體對應檔案讀出...")
ShowBlogInfo(blogObj)
End Using
End Using

'顯示寫入後資訊
Console.WriteLine(New String("="c, 50))
Console.WriteLine("記憶體物件的資料內容...")
ShowBlogInfo(levelUpBlog)
End Using
End Sub

Private Sub ShowBlogInfo(ByVal blog As Blog)
With blog
Console.WriteLine("Title: {0}", .Title)
Console.WriteLine("Owner: {0}", .Owner)
Console.WriteLine("Article Count: {0}", .Articles.Count)
If .Articles.Count > 0 Then
Console.WriteLine()
Console.WriteLine("Article")
For Each content As String In .Articles
Console.WriteLine(content)
Next
End If
End With
End Sub

End Module

<Serializable()> _
Class Blog
Property Title As String
Property Owner As String
Property Articles As New StringCollection
End Class


運行結果如下:

image


可從中看到記憶體對應檔案的內容只需要彼此知道其對應的Key,就可以存取到同一個記憶體對應檔案,在處理結束後,記憶體對應檔案中的內容會被回收,所以記憶體中的變數值並不會隨之改變。


接著將上面的範例改成保存的記憶體對應檔案物件的使用範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Collections.Specialized
Imports System.Runtime.Serialization.Formatters.Binary

Module Module1

Sub Main()
Const capacity As Integer = 1024
Const mmfKey As String = "Larry"
Const file As String = "Blog.xml"
Dim levelUpBlog As New Blog With {.Title = "Level Up", .Owner = "Larry"}

'顯示寫入前資訊
Console.WriteLine("資料寫入記憶體對應檔案前...")
ShowBlogInfo(levelUpBlog)

'Write to memory
Using mmf As MemoryMappedFile = MemoryMappedFile.CreateFromFile(New FileStream(file, FileMode.Create), mmfKey, capacity, MemoryMappedFileAccess.ReadWrite, Nothing, HandleInheritability.None, False)
Using mmvs = mmf.CreateViewStream
Dim bs As New BinaryFormatter
bs.Serialize(mmvs, levelUpBlog)
End Using

'Read from memory
Using existedMMF As MemoryMappedFile = MemoryMappedFile.OpenExisting(mmfKey)
Using mmvs = existedMMF.CreateViewStream
Dim bs As New BinaryFormatter
Dim blogObj As Blog = bs.Deserialize(mmvs)

Console.WriteLine(New String("="c, 50))
Console.WriteLine("資料從記憶體對應檔案讀出並增加文章...")
ShowBlogInfo(blogObj)

blogObj.Articles.Add("文章一 初來點部落:...(略)...")
mmvs.Seek(0, IO.SeekOrigin.Begin)
bs.Serialize(mmvs, blogObj)
End Using
End Using

'Read from memory
Using existedMMF As MemoryMappedFile = MemoryMappedFile.OpenExisting(mmfKey)
Using mmvs = existedMMF.CreateViewStream
Dim bs As New BinaryFormatter
Dim blogObj As Blog = bs.Deserialize(mmvs)

Console.WriteLine(New String("="c, 50))
Console.WriteLine("資料從記憶體對應檔案讀出...")
ShowBlogInfo(blogObj)
End Using
End Using

'顯示寫入後資訊
Console.WriteLine(New String("="c, 50))
Console.WriteLine("記憶體物件的資料內容...")
ShowBlogInfo(levelUpBlog)
End Using

Using fs As New FileStream(file, FileMode.Open)
Dim bs As New BinaryFormatter
Dim blogObj As Blog = bs.Deserialize(fs)

Console.WriteLine(New String("="c, 50))
Console.WriteLine("檔案儲存的資料內容...")
ShowBlogInfo(blogObj)
End Using
End Sub

Private Sub ShowBlogInfo(ByVal blog As Blog)
With blog
Console.WriteLine("Title: {0}", .Title)
Console.WriteLine("Owner: {0}", .Owner)
Console.WriteLine("Article Count: {0}", .Articles.Count)
If .Articles.Count > 0 Then
Console.WriteLine()
Console.WriteLine("Article")
For Each content As String In .Articles
Console.WriteLine(content)
Next
End If
End With
End Sub

End Module

<Serializable()> _
Class Blog
Property Title As String
Property Owner As String
Property Articles As New StringCollection
End Class


運行結果如下:

image


從非保存的記憶體對應檔案物件改成保存的記憶體對應檔案物件,我們只需替換使用對應的方法產生記憶體對應檔案物件即可,程式上不需做太多的修改,從範例中我們也可以看到,在處理完畢後,對應到磁碟內的檔案也會反映我們在記憶體對應檔案物件中所做的修改。


最後再來看一下如何使用非保存的記憶體對應檔案來做不同處理序間的溝通,首先我們先設計如下介面:

image


撰寫如下程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Imports System.IO.MemoryMappedFiles

Public Class Form1
Const MMF_KEY As String = "MMFMsg"
Const CAPACITY As Integer = 1024

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim mmf As MemoryMappedFile = MemoryMappedFile.CreateOrOpen(MMF_KEY, CAPACITY)
Using mmvs = mmf.CreateViewStream()
Using br As New IO.BinaryReader(mmvs)
tbxMonitor.Text = br.ReadString
End Using
End Using
End Sub

Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
Dim mmf As MemoryMappedFile = MemoryMappedFile.CreateOrOpen(MMF_KEY, CAPACITY)
Using mmvs = mmf.CreateViewStream()
Using bw As New IO.BinaryWriter(mmvs)
bw.Write(tbxSend.Text)
End Using
End Using
End Sub
End Class


將程式運行一次以上,並在發送區填入要發送的訊息,按下發送按鈕,就可以看到所有開啟的程式都會收到相同的訊息。

image