2012-05-05 5 views
2

У меня есть файл с номерами, разделенными пробелами. Это размер около 1 ГБ, и я хочу получить цифры от него. Я решил использовать файлы с памятью для быстрого чтения, но я не понимаю, как это сделать. Я пытался делать дальше:Как получить int из памяти Mapped File

var mmf = MemoryMappedFile.CreateFromFile("test", FileMode.Open, "myFile"); 
var mmfa = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); 
var nums = new int[6]; 
var a = mmfa.ReadArray<int>(0, nums, 0, 6); 

Но если "тест" содержит только "01" в NUM [0] Я получаю 12337. 12337 = 48 * 256 + 49. Я искал в Интернете, но ничего не нашел о моем вопросе. только о байтовых массивах или межпроцессной коммуникации. Можете ли вы показать мне, как получить 1 в num [0]?

+1

Если ваши данные ASCII, вам необходимо проанализировать его, прежде чем вы сможете преобразовать его в int. Другой вариант - написать конвертер, который читает ваш файл по строке и записывать целые числа в виде двоичных значений в файл. Затем вы можете использовать свой подход выше, чтобы прочитать целые числа. –

+0

@Alois: хороший catch, OP фактически хочет конвертировать из ASCII в двоичное представление. – Vlad

+0

Если скорость ваша главная проблема, это может помочь посмотреть: http://stackoverflow.com/questions/7153315/how-to-parse-a-text-file-in-c-sharp-and-be-io- bound –

ответ

3

Следующий пример будет считываться из целых чисел ASCII из файла с отображением памяти самым быстрым способом без создания каких-либо строк. Решение, предлагаемое MiMo, намного медленнее. Он работает со скоростью 5 Мбайт/с, что вам не поможет. Самая большая проблема в решении MiMo заключается в том, что он вызывает метод (Read) для каждого символа, который стоит критического коэффициента производительности 15. Интересно, почему вы приняли его решение, если ваша оригинальная проблема заключалась в том, что у вас была проблема с производительностью. Вы можете получить 20 МБ/с с помощью немого строкового считывателя и разбора строки в целое число. Чтобы получить каждый байт с помощью вызова метода, он разрушает вашу возможную производительность чтения.

Код ниже отображает файл в 200 МБ фрагментов, чтобы предотвратить заполнение 32-разрядного адресного пространства. Затем он просматривает буфер через указатель байта, который очень быстрый. Целочисленный анализ легко, если вы не учитываете локализацию. Интересно, что если я создаю представление для отображения, то единственный способ получить указатель на буфер просмотра не позволит мне начать с отображаемой области.

Я бы счел это bug in the .NET Framwork, который до сих пор не исправлен в .NET 4.5. Буфер SafeMemoryMappedViewHandle выделяется с помощью гранулярности выделения ОС. Если вы переходите к некоторому смещению, вы получаете указатель назад, который все еще указывает на начало буфера.Это очень неудачно, потому что это делает разницу между 5MB/s и 77MB/s в производительности синтаксического анализа.

Did read 258.888.890 bytes with 77 MB/s 


using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.IO; 
using System.IO.MemoryMappedFiles; 
using System.Runtime.InteropServices; 

unsafe class Program 
{ 
    static void Main(string[] args) 
    { 
     new Program().Start(); 
    } 

    private void Start() 
    { 
     var sw = Stopwatch.StartNew(); 
     string fileName = @"C:\Source\BigFile.txt";//@"C:\Source\Numbers.txt"; 
     var file = MemoryMappedFile.CreateFromFile(fileName); 
     var fileSize = new FileInfo(fileName).Length; 
     int viewSize = 200 * 100 * 1000; 
     long offset = 0; 
     for (; offset < fileSize-viewSize; offset +=viewSize) // create 200 MB views 
     { 
      using (var accessor = file.CreateViewAccessor(offset, viewSize)) 
      { 
       int unReadBytes = ReadData(accessor, offset); 
       offset -= unReadBytes; 
      } 
     } 

     using (var rest = file.CreateViewAccessor(offset, fileSize - offset)) 
     { 
      ReadData(rest, offset); 
     } 
     sw.Stop(); 
     Console.WriteLine("Did read {0:N0} bytes with {1:F0} MB/s", fileSize, (fileSize/(1024 * 1024))/sw.Elapsed.TotalSeconds); 
    } 


    List<int> Data = new List<int>(); 

    private int ReadData(MemoryMappedViewAccessor accessor, long offset) 
    { 
     using(var safeViewHandle = accessor.SafeMemoryMappedViewHandle) 
     { 
      byte* pStart = null; 
      safeViewHandle.AcquirePointer(ref pStart); 
      ulong correction = 0; 
      // needed to correct offset because the view handle does not start at the offset specified in the CreateAccessor call 
      // This makes AquirePointer nearly useless. 
      // http://connect.microsoft.com/VisualStudio/feedback/details/537635/no-way-to-determine-internal-offset-used-by-memorymappedviewaccessor-makes-safememorymappedviewhandle-property-unusable 
      pStart = Helper.Pointer(pStart, offset, out correction); 
      var len = safeViewHandle.ByteLength - correction; 
      bool digitFound = false; 
      int curInt = 0; 
      byte current =0; 
      for (ulong i = 0; i < len; i++) 
      { 
       current = *(pStart + i); 
       if (current == (byte)' ' && digitFound) 
       { 
        Data.Add(curInt); 
        // Console.WriteLine("Add {0}", curInt); 
        digitFound = false; 
        curInt = 0; 
       } 
       else 
       { 
        curInt = curInt * 10 + (current - '0'); 
        digitFound = true; 
       } 
      } 

      // scan backwards to find partial read number 
      int unread = 0; 
      if (curInt != 0 && digitFound) 
      { 
       byte* pEnd = pStart + len; 
       while (true) 
       { 
        pEnd--; 
        if (*pEnd == (byte)' ' || pEnd == pStart) 
        { 
         break; 
        } 
        unread++; 

       } 
      } 

      safeViewHandle.ReleasePointer(); 
      return unread; 
     } 
    } 

    public unsafe static class Helper 
    { 
     static SYSTEM_INFO info; 

     static Helper() 
     { 
      GetSystemInfo(ref info); 
     } 

     public static byte* Pointer(byte *pByte, long offset, out ulong diff) 
     { 
      var num = offset % info.dwAllocationGranularity; 
      diff = (ulong)num; // return difference 

      byte* tmp_ptr = pByte; 

      tmp_ptr += num; 

      return tmp_ptr; 
     } 

     [DllImport("kernel32.dll", SetLastError = true)] 
     internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); 

     internal struct SYSTEM_INFO 
     { 
      internal int dwOemId; 
      internal int dwPageSize; 
      internal IntPtr lpMinimumApplicationAddress; 
      internal IntPtr lpMaximumApplicationAddress; 
      internal IntPtr dwActiveProcessorMask; 
      internal int dwNumberOfProcessors; 
      internal int dwProcessorType; 
      internal int dwAllocationGranularity; 
      internal short wProcessorLevel; 
      internal short wProcessorRevision; 
     } 
    } 

    void GenerateNumbers() 
    { 
     using (var file = File.CreateText(@"C:\Source\BigFile.txt")) 
     { 
      for (int i = 0; i < 30 * 1000 * 1000; i++) 
      { 
       file.Write(i.ToString() + " "); 
      } 
     } 
    } 

} 
+0

На самом деле я не думаю, что файлы с отображением памяти сделают любое ускорение по сравнению с обычными файлами. – Vlad

+0

Если скорость является основной проблемой, двоичный формат будет способом. –

+0

OP упоминает, что он не управляет входным форматом :( – Vlad

0

48 = 0x30 = '0', 49 = 0x31 = '1'

Таким образом, вы получаете действительно ваших персонажей, они просто ASCII кодировке.

Строка «01» занимает 2 байта, которые вписываются в один int, поэтому вы получаете их оба в одном int. Если вы хотите получить их отдельно, вам нужно задать массив из byte.


Edit: в случае, когда «01» должно быть разобрано в постоянную 1, то есть, из представления ASCII в двоичный файл, вам нужно идти другим путем. Я хотел бы предложить

  1. не используют в памяти, в файл,
  2. прочитать файл с StreamReader построчно (пример here)
  3. разделения каждой строки на куски с помощью String.split
  4. разобрать каждый кусок в число с помощью string.Parse
+0

На самом деле, я хочу знать, как читать все числа из файла. –

+0

@Vasilii: см. Отредактированный пост: вам нужно прочитать символы, а не ints. – Vlad

+0

ОК, так что нет способа получить фактически INT из файла? после того, как я получу массив байтов, мне придется его разобрать? –

1

Вам необходимо проанализировать содержимое файла, преобразование символов в цифры - что-то вроде этого:

List<int> nums = new List<int>(); 
long curPos = 0; 
int curV = 0; 
bool hasCurV = false; 
while (curPos < mmfa.Capacity) { 
    byte c; 
    mmfa.Read(curPos++, out c); 
    if (c == 0) { 
    break; 
    } 
    if (c == 32) { 
    if (hasCurV) { 
     nums.Add(curV); 
     curV = 0; 
    } 
    hasCurV = false; 
    } else { 
    curV = checked(curV*10 + (int)(c-48)); 
    hasCurV = true; 
    } 
} 
if (hasCurV) { 
    nums.Add(curV); 
} 

Предполагая, что mmfa.Capacity - это общее количество символов для чтения и что файл содержит только цифры, разделенные пробелом (т. нет конечных линий или других пробелов)

+0

вам также нужно обработать новые строки :) и следить за переполнениями – Vlad

+2

, и вы не сбрасываете 'hasCurV'. ручная разборка сложна – Vlad

+0

@ Vlad: thanks - fixed (кроме новой строки - «если ... нет конечных линий или других пробелов») – MiMo