Эффективной обработки больших текстовых файлов на C#
мне нужно прочитать большой текстовый файл, разделенный пробелом, и подсчитать количество экземпляров каждого кода в файле. По сути, это результаты проведения некоторых экспериментов сотни тысяч раз. Система выплевывает текстовый файл, который выглядит примерно так:
A7PS A8PN A6PP23 ...
и есть буквально сотни тысяч этих записей, и мне нужно подсчитать случаи каждого из кодов.
Я думаю, я мог бы просто открыть StreamReader
и пройти строка за строкой, разделяя символ пробела. Просмотр, если код уже был обнаружен и добавление 1 к количеству этого кода. Однако это, вероятно, довольно наивно, учитывая размер данных.
кто-нибудь знает эффективный алгоритм для обработки такого рода обработку?
обновление :
хорошо, поэтому консенсус, похоже, мой подход находится в правильном направлении
то, что мне было бы интересно услышать, - это такие вещи , как эффективнее-StreamReader. TextReader, BinaryReader
какова лучшая структура для хранения моего словаря результатов? HashTable, SortedList, HybridDictionary
Если нет разрывов строк в файле (мне еще не дали образец), просто разделение всего этого на пространстве будет неэффективным?
по сути, я смотрю на то, чтобы сделать его как можно более эффективным
еще раз спасибо
8 ответов
ваш подход выглядит хорошо.
- читать в строке в строке
- разделить каждую строку на пробел
- добавить запись в словарь если он еще не существует и если он существует, сделайте значение++
Я бы сказал, что в целом ваш подход правильный, но есть возможности для параллелизма. Я бы предложил вам запустить несколько потоков или задач (в .NET 4) каждый разбор части/фрагмента файла. Также вместо чтения строки за строкой, чтение в куске байтов-даст лучшую производительность с точки зрения ввода-вывода диска.
редактировать: вот план решения.
- предположим, мы будем обрабатывать M кусков N символов в то время (потому что мы хотим предельный объем памяти необходимое и количество используемых потоков).
- выделить N * M символьный буфер. Мы будем использовать этот буфер циклически.
- будет использовать производитель-потребитель шаблон. Производитель заполнит буфер. Он попытаюсь найти границу слова рядом граница куска (т. е. около каждого Nth характер.) Итак, у нас будет M кусков приблизительно N символов с начала и конечный индекс в буфере
- теперь запустите M рабочих потоков для обработки каждого куска. Каждый работник будет использовать свой собственный словарь для подсчета слов-это устранит необходимость синхронизации потоков.
- будет агрегировать результаты в конце итерации. Процесс должен повторяться до тех пор, пока не будет прочитан весь файл.
конечно, я предполагаю действительно огромные файлы для принятия этого подхода. Я, вероятно, буду использовать старый стиль поиска символов в буфере, чтобы найти код поиска границы слова как небезопасный, чтобы избежать связанных проверок.
Я согласен с комментарием 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 секунды.
это кажется довольно эффективным для меня.