Поиск на сайте
Главная Журнал Форум Wiki DRKB Страны мира

Работа с большими Memory-Mapped Files в VB 2010

Вообще говоря, чтобы произвести какие-то манипуляции с файлом, его необходимо сперва считать в оперативную память сразу целиком или последовательно. (Прямой доступ (Random Access) так же поддерживается в обычных потоковых классах.) Проблема в том, что очень большие файлы легко съедают память, особенно на 32-битные машинах с 2-х гигабайтным ограничением. Технология отображаемых в память файлов (Memory-Mapped Files), это своего рода решение этой проблемы, позволяющая получить доступ к любой части файла, как если бы он был загружен в память. У отображаемых в память файлов нет никаких ограничений на размер.

Стандартный файловый ввод/вывод по сравнению с отображаемыми в память файлами

Использование обычной библиотеки System.IO подразумевает считывание файла в память и дальнейшую работу с ним, а если учесть ограничения на выделение памяти в 32-битных системах, то можно легко предположить, что потоковые системы доступа к файлам, не очень подходят для серверов баз данных, больших текстовых документов или чего-нибудь, основанного на больших файлах.

.NET Framework 4 позволяет сопостовлять отображаемые в память файлы как с физическими, так и с логическими файлами. Кроме того есть поддержка потоковой работы с отображаемыми файлами.

Для отображаемых файлов в 32-битных системах по-прежнему существует ограничение в 2 гигабайта, но это два гигабайта на непрерывный кусок данных, а таких кусков можно создать несколько. Идея заключается в том, технология отображаемых файлов позволяет из разных процессов обращаться к одному и тому же файлу, тем самым снимая 2-х гигабайтное ограничение (для 32-битных систем). Естевственно что синхронизация изменений между процессами в таком случае уже ложится на плечи программиста..

Доступ файла с помощью обычного файлового ввода/вывода

Задача, которую выполняет нижеприведённый пример - считывание файла и подсчёт частоты повторяющихся слов в тексте. Листинг 1 демонстрирует решение этой задичи при помощи традиционного чтения файла.

  Imports System.IO
  Imports System.IO.MemoryMappedFiles
  
  Public Class Form1
  
    Private Const filename As String = "..\..\cicero.txt"
    Private splitChars() As Char =
    {",", ".", " ", ":", ";", "/", "\", "[", "]",
      "{", "}", "=", "+", "-", "*", "`", "'", "1", "2",
      "3", "4", "5", "6", "7", "8", "9", "0", "(", ")", "!"}
  
    Private Sub Form1_Load(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles MyBase.Load
  
      Me.DoubleBuffered = True
    End Sub
  
    Private Sub CountWordsWithStreamToolStripMenuItem_Click(
      ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles CountWordsWithStreamToolStripMenuItem.Click
  
      hash.Clear()
      Dim lines = File.ReadLines(filename)
      Dim words() As String
  
      For Each line In lines
        words = line.Split(splitChars, StringSplitOptions.RemoveEmptyEntries)
        For Each word In words
          AddWordToHash(word)
          Application.DoEvents()
        Next
      Next
      Dump(hash)
    End Sub
  
    Private Sub Dump(ByVal hash As Hashtable)
      TextBox1.Text = String.Format("Number of words: {0}", hash.Count)
      For i = 1 To hash.Count - 1
        TextBox1.Text += vbCrLf +
          String.Format("{0} occurs {1} times", hash.Keys()(i), hash(hash.Keys()(i)))
      Next
    End Sub
  
    
    Private Sub ClearAllToolStripMenuItem_Click(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles ClearAllToolStripMenuItem.Click
      TextBox1.Clear()
    End Sub
  
    Private hash As Hashtable = New Hashtable()
    Private Sub AddWordToHash(ByVal word As String)
      word = word.ToLower()
      If (hash(word) Is Nothing) Then
        hash.Add(word, 1)
      Else
        hash(word) = CType(hash(word), Long) + 1
      End If
      word = ""
    End Sub
  
  End Class

Листинг 1: Подсчёт частоты слов с использованием стандартного файлового доступа.

Код в листинге 1 тупо считывает все строки текстового файла. Сплитит каждую строку в массив используя массив разделителей, а затем помещает каждое слово в хэш-таблицу. Каждый раз, когда попадается слово, присутствующее в хэш-таблице (ключевое слово), значение счетчика (хэш значение) в этом месте увеличивается. Когда код завершает свою работу, то мы получаем частоту повторения слов. Такой подход можно использовать для таких задач как поиск и замена, подсветки слов, проверки орфографии текстов и т.д. - всё зависит от Ваших идей.

Вывод результата листинга 1 после обработки какого-нибудь текста Цицерона показан на рисунке 1. Цицерон был римским философом, а текст был взят с http://www.blindtextgenerator.com.


Рисунок 1: частота повторения слов в тексте из Цицерона.

Доступ к файлу при помощи MemoryMappedFile

Отображаемые в памяти файлы можно сопоставлять как логическими, так и с физическими файлами. Для этого используются классы MemoryMappedViewAccessor, MemoryMappedFile или MemoryMappedViewStream.

Есть два типа MemoryMappedFile: сохраняемые (Persisted) и не сохраняемые (Non-Persisted). Сохраняемые записываются в файловой системе и остаются после завершения процесса. Несохраняемые не связаны с физическим файлом на диске и по завершению процесса все данные в таком файле теряются, а память очищается сборщиком мусора.

Если вы хотите работать с MemoryMappedFiles как с обычными файловыми потоками, то необходимо создать экземляр MemoryMappedViewStream. Методы в этом классе совпадают с методами в filestream. Для работы с сохраняемыми или несохраняемыми отображениями отличным от filestream способом, то следует использовать MemoryMappedViewAccessor. Потоковый стиль подразумевает использование методов типа Read, Write и Seek, в то время как непотоковый стиль это обращение к данным через собственные типы, структуры или массивы структур.

В листинге 2 показано, как прочитать все символы в файл, с использованием MemoryMappedFile и подсчета частоты слов. Обратите внимание, что в данном примере все данные файла не доступны сразу.

  Imports System.IO
  Imports System.IO.MemoryMappedFiles
  
  Public Class Form1
  
    Private Const filename As String = "..\..\cicero.txt"
    Private splitChars() As Char =
    {",", ".", " ", ":", ";", "/", "\", "[", "]",
      "{", "}", "=", "+", "-", "*", "`", "'", "1", "2",
      "3", "4", "5", "6", "7", "8", "9", "0", "(", ")", "!"}
  
    Private Sub Form1_Load(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles MyBase.Load
  
      Me.DoubleBuffered = True
    End Sub
  
    Private Sub Dump(ByVal hash As Hashtable)
      TextBox1.Text = String.Format("Number of words: {0}", hash.Count)
      For i = 1 To hash.Count - 1
        TextBox1.Text += vbCrLf +
          String.Format("{0} occurs {1} times", hash.Keys()(i), hash(hash.Keys()(i)))
      Next
    End Sub
  
    
    Private Sub ClearAllToolStripMenuItem_Click(ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles ClearAllToolStripMenuItem.Click
      TextBox1.Clear()
    End Sub
  
    Private hash As Hashtable = New Hashtable()
    Private Sub AddWordToHash(ByVal word As String)
      word = word.ToLower()
      If (hash(word) Is Nothing) Then
        hash.Add(word, 1)
      Else
        hash(word) = CType(hash(word), Long) + 1
      End If
      word = ""
    End Sub
  
    Private Sub CountWordsWithMappedFileToolStripMenuItem_Click(
    ByVal sender As System.Object,
    ByVal e As System.EventArgs) Handles CountWordsWithMappedFileToolStripMenuItem.Click
  
      hash.Clear()
      Dim word As String = ""
      Dim ch As Char = ""
  
      Dim mappedFile As MemoryMappedFile =
        MemoryMappedFile.CreateFromFile(Path.GetFullPath(filename))
      Try
      Dim position As Long = 0
        Using accessor = mappedFile.CreateViewAccessor()
            While (position < accessor.Capacity)
              ch = Microsoft.VisualBasic.ChrW(accessor.ReadByte(position))
  
              If (Not splitChars.Contains(ch)) Then
                word += ch
              Else
               AddWordToHash(word)
               word = ""
              End If
  
              position += 1
              Application.DoEvents()
            End While
        End Using
      Finally
        mappedFile.Dispose()
      End Try
  
      Dump(hash)
    End Sub
  End Class

Листинг 2: Подсчёт частоты слов с использованием MemoryMappedFile.

Код в листинге 2 выполняет ту же задачу, но работает по-другому. Первая строка очищает хранилище хэш-таблицы. Метод CreateFromFile создает экземпляр MemoryMappedFile из сохраняемого файла - файла на диске (есть и другие методы для работы с сохраняемыми и несохраняемыми файлами). Вызов MemoryMappedFile.CreateViewAccessor без параметров отображаем весь файл в память, возвращая MemoryMappedViewAccessor. MemoryMappedFile и MemoryMappedViewAccessor являются IDisposable, поэтому необходимо использовать конструкцию Try Finally и явно вызовать команды Dispose или Using. (Команда Using обычно на стадии компиляции превращается в блок try finally)

Поскольку String не является дискретным типом MemoryMappedFiles, соответственно пример не читает построчно, а вместо этого происходит считывание байтов, выделяя слова при помощи заданных символов-разделителей. Как и в предыдущем примере каждое уникальное слово вставляется в хэш-таблицу, а число вставок подсчитывается. При небольшом размере текстовых файлов у обоих примеров показатели будут различаться несущественно, однако при большом объёме информации традиционное считывание файла приведёт к серьёзному замедлению процесса и тогда лучше использовать MemoryMappedFile.

Например, в листинге 3 текстовый файл разбивается на пару кусков при помощи MemoryMappedFile, чтобы проиллюстрировать, что MemoryMappedFiles поддерживает работу одновременно нескольких процессов с одним и тем же файлом.

  Imports System.IO
  Imports System.IO.MemoryMappedFiles
  Imports System.ComponentModel
  Imports System.Collections.Concurrent
  
  Module Module1
  
      Private Const filename As String = "..\..\cicero.txt"
      
      Private fullpath As String = Path.GetFullPath(filename)
      Private info As FileInfo = New FileInfo(fullpath)
      Private hash As ConcurrentDictionary(Of String, Long) = 
        New ConcurrentDictionary(Of String, Long)()
  
  
      Sub Main()
        Dim mapped As MemoryMappedFile = MemoryMappedFile.CreateFromFile(fullpath,
          FileMode.Open, "Mapped1")
        Dim worker1 As BackgroundWorker = New BackgroundWorker()
        Dim worker2 As BackgroundWorker = New BackgroundWorker()
        Try
          Dim w1 As MyWorker = New MyWorker(0, info.Length / 2, hash)
          Dim w2 As MyWorker = New MyWorker(info.Length / 2, info.Length, hash)
  
          AddHandler worker1.DoWork, AddressOf w1.Work
          AddHandler worker2.DoWork, AddressOf w2.Work
          worker1.RunWorkerAsync(mapped)
          worker2.RunWorkerAsync(mapped)
  
          While (worker1.IsBusy Or worker2.IsBusy)
  
          End While
  
        Finally
          mapped.Dispose()
          worker1.Dispose()
          worker2.Dispose()
        End Try
  
        Dump(hash)
        Console.ReadLine()
  
      End Sub
  
      Sub Dump(ByVal hash As ConcurrentDictionary(Of String, Long))
        Console.WriteLine("Number of words: {0}", hash.Count)
  
        Dim ordered = From k In hash.Keys
                      Order By k
                      Select New With {.Word = k, .Count = hash(k)}
  
                      
        Array.ForEach(ordered.ToArray(), Sub(o)
          Console.WriteLine("{0} occurs {1} times", o.Word, o.Count)
        End Sub)
      End Sub
  
  End Module
  
  
  Public Class MyWorker
    Private splitChars() As Char =
      {",", ".", " ", ":", ";", "/", "\", "[", "]",
      "{", "}", "=", "+", "-", "*", "`", "'", "1", "2",
      "3", "4", "5", "6", "7", "8", "9", "0", "(", ")", "!"}
  
    Private Property start As Long
    Private Property finish As Long
    Private hash As ConcurrentDictionary(Of String, Long)
    Private worker As BackgroundWorker = New BackgroundWorker()
  
    ''' <summary>
    ''' Initializes a new instance of the MyWorker class.
    ''' </summary>
    ''' <param name="Hash"></param>
    Public Sub New(ByVal Start As Long, ByVal Finish As Long,
      ByVal Hash As ConcurrentDictionary(Of String, Long))
        Me.start = Start
        Me.finish = Finish
        Me.hash = Hash
    End Sub
  
  
    Private Sub AddWordToHash(ByVal word As String)
      If (Not hash.ContainsKey(word)) Then
        hash.TryAdd(word, 1)
      Else
        hash(word) = CType(hash(word), Long) + 1
      End If
    End Sub
  
    Public Sub Work(ByVal sender As Object, ByVal e As DoWorkEventArgs)
      Dim mapped As MemoryMappedFile = DirectCast(e.Argument, MemoryMappedFile)
      Dim position As Long = start
      Dim ch As Char
      Dim word As String = ""
      Using accessor = mapped.CreateViewAccessor()
        While (position < finish)
          ch = Microsoft.VisualBasic.ChrW(accessor.ReadByte(position))
          If (Not splitChars.Contains(ch)) Then
            word += ch
          Else
            AddWordToHash(word)
            word = ""
          End If
          position += 1
        End While
      End Using
    End Sub
  End Class

Листинг 3: Использование класса BackgroundWorker для раздельного чтения отображаемого файла в несколько потоков.

В этом примере используется ConcurrentDictionary, который является потокобезопасным. Файл делится пополам и каждая половина обрабатывается своим экземпляром BackgroundWorker.




Основные разделы сайта


 

Реклама Вакансия техник-электрик в Санкт Петербурге
скульптор тела массажер Relax and Tone.
Тонус-Клуб в Москве. Скидки: массажер relax and tone. Массажер Relax and Tone - 1550р.