| Главная | Журнал | Форум | 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.
Доступ к файлу при помощи 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. |
Основные разделы сайта
|
|
|