.NET 4.0 New Feature - Memory Mapped File

.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等方法來建立記憶體對應檔案物件。

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

 

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

 

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

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

 

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

 

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

    

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

	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,就可以存取到同一個記憶體對應檔案,在處理結束後,記憶體對應檔案中的內容會被回收,所以記憶體中的變數值並不會隨之改變。

 

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

	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

 

撰寫如下程式碼:

	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

 

Download

MMF.zip

 

Link