Эффективной обработки больших текстовых файлов на C#

мне нужно прочитать большой текстовый файл, разделенный пробелом, и подсчитать количество экземпляров каждого кода в файле. По сути, это результаты проведения некоторых экспериментов сотни тысяч раз. Система выплевывает текстовый файл, который выглядит примерно так:

A7PS A8PN A6PP23 ...

и есть буквально сотни тысяч этих записей, и мне нужно подсчитать случаи каждого из кодов.

Я думаю, я мог бы просто открыть StreamReader и пройти строка за строкой, разделяя символ пробела. Просмотр, если код уже был обнаружен и добавление 1 к количеству этого кода. Однако это, вероятно, довольно наивно, учитывая размер данных.

кто-нибудь знает эффективный алгоритм для обработки такого рода обработку?

обновление :

хорошо, поэтому консенсус, похоже, мой подход находится в правильном направлении

то, что мне было бы интересно услышать, - это такие вещи , как эффективнее-StreamReader. TextReader, BinaryReader

какова лучшая структура для хранения моего словаря результатов? HashTable, SortedList, HybridDictionary

Если нет разрывов строк в файле (мне еще не дали образец), просто разделение всего этого на пространстве будет неэффективным?

по сути, я смотрю на то, чтобы сделать его как можно более эффективным

еще раз спасибо

8 ответов


ваш подход выглядит хорошо.

  1. читать в строке в строке
  2. разделить каждую строку на пробел
  3. добавить запись в словарь если он еще не существует и если он существует, сделайте значение++

Я бы сказал, что в целом ваш подход правильный, но есть возможности для параллелизма. Я бы предложил вам запустить несколько потоков или задач (в .NET 4) каждый разбор части/фрагмента файла. Также вместо чтения строки за строкой, чтение в куске байтов-даст лучшую производительность с точки зрения ввода-вывода диска.

редактировать: вот план решения.

  1. предположим, мы будем обрабатывать M кусков N символов в то время (потому что мы хотим предельный объем памяти необходимое и количество используемых потоков).
  2. выделить N * M символьный буфер. Мы будем использовать этот буфер циклически.
  3. будет использовать производитель-потребитель шаблон. Производитель заполнит буфер. Он попытаюсь найти границу слова рядом граница куска (т. е. около каждого Nth характер.) Итак, у нас будет M кусков приблизительно N символов с начала и конечный индекс в буфере
  4. теперь запустите M рабочих потоков для обработки каждого куска. Каждый работник будет использовать свой собственный словарь для подсчета слов-это устранит необходимость синхронизации потоков.
  5. будет агрегировать результаты в конце итерации. Процесс должен повторяться до тех пор, пока не будет прочитан весь файл.

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


Я согласен с комментарием PoweRoy: почему бы не попробовать? Может быть, на практике нет никаких проблем.

Если вам нужно что-то еще, можно попробовать написать код, который занимает Stream и возвращает IEnumerable<string>. Он будет читать символы из входного потока по одному за раз - если вам нужна буферизация для повышения эффективности, вы всегда можете обернуть FileStream вы фактически даете этот код в BufferStream - и проверяет, является ли это пробелом (или возможно EOL?). Если это не так, он добавит символ к строковому буферу (возможно,StringBuilder?), но если это будет yield return текущий строковый буфер и очистите его.

после этого вы можете просто foreach над результатом вызова этого кода на содержание файла, и вы получите коды из файла один за другим.

затем вы можете использовать какую-то структуру данных, такую как Dictionary<string,int> чтобы подсчитать количество вхождений для каждого кода, сохраняя код как ключ, и подсчет как значение. Но этот шаг был бы таким же, если бы вы прочитайте файл строка за строкой и используйте string.Split разделить их на пробелы.


Если вы хотите попробовать что-то другое, вы можете попробовать использовать BinaryReader и считывайте поток байт за байтом и увеличивайте счетчик на один каждый раз, когда вы сталкиваетесь с пробелом.


сотен тысяч записей не так много. Я бы использовал Dictionary<string,int>. Для хранения ключа и графа.

но если вы столкнулись с проблемами памяти, почему бы не использовать базу данных, даже такую базу данных, как SQL Compact или SQLite. Создайте таблицу с записью, содержащей ключ и счетчик.

сохранение данных в памяти является самым быстрым для небольших объемов данных, но когда вы достигнете пределов памяти компьютера, в базе будет быстрее.


на самом базовом уровне, я бы начал с Dictionary<string, int>, строку.разделите документ на пробелы и продолжайте подсчет с помощью простого анализа этих данных.

строку.split-относительно надежный метод, который, и кто-то обязательно исправит меня, если я ошибаюсь, был построен для использования регулярных выражений и намного сложнее, чем вам нужно для этого сценария.

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

Иэн


Если нет других ограничений, вы должны прочитать весь файл как вы описали.

чтобы сохранить коды и количество, вы должны использовать datastructure, которая позволяет искать и вставлять в O (log n) время. SortedDictionary сделает это на C#.

EDIT:

какова лучшая структура для хранения моего словаря результатов? HashTable, SortedList, HybridDictionary

потому что сортированный порядок кажется не требуется HybridDictionary или словарь будет работать лучше в большинстве случаев. SortedList, вероятно, будет самым медленным решением, потому что вставки принимают O(n). Вы должны выполнить некоторые тесты с различными реализациями, если производительность так важна.


    static string LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static string NUMBERS = "1234567890";
    static Random rdGen = new Random();
    static Dictionary<string, int> myDic = new Dictionary<string, int>();
    static void WriteTest(int max)
    {
        myDic = new Dictionary<string, int>();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < max; i++)
        {
            string code = LETTERS[rdGen.Next(0, 26)].ToString() + NUMBERS[rdGen.Next(0, 10)].ToString() + LETTERS[rdGen.Next(0, 26)].ToString() + LETTERS[rdGen.Next(0, 26)].ToString();
            if (myDic.ContainsKey(code)) myDic[code]++;
            else
            {
                myDic[code] = 1;
            }
        }
        sw.Stop();
        Console.WriteLine(max.ToString() + " itérations : " + sw.ElapsedMilliseconds.ToString());

    }

WriteTest (10000000); // занимает 7,5 секунды.

это кажется довольно эффективным для меня.